Form adalah bagian penting dalam hampir setiap aplikasi web: formulir kontak, tambah data, edit profil, pencarian, hingga filter. Di SvelteKit, pembuatan form terasa sederhana karena Svelte menyediakan mekanisme reaktivitas yang langsung bisa dipakai untuk menghubungkan elemen input dengan state komponen. Pada artikel ini, kita akan fokus pada praktik membuat form dasar di dalam project SvelteKit, khususnya penggunaan input text, textarea, select, bind:value, submit form, dan validasi ringan di sisi client.

Contoh yang digunakan adalah form kontak sederhana. Pendekatan ini cocok sebagai fondasi sebelum Anda masuk ke validasi server, action form SvelteKit, atau integrasi API yang lebih kompleks.

Menyiapkan Halaman Form di SvelteKit

Di dalam project SvelteKit, Anda bisa membuat halaman baru, misalnya di file src/routes/contact/+page.svelte. Kita akan menyimpan state form langsung di komponen dan memanfaatkan bind:value agar nilai input selalu sinkron dengan variabel JavaScript.

Struktur awalnya seperti ini:

<script>
  let name = '';
  let email = '';
  let category = 'general';
  let message = '';
</script>

<form>
  <input type="text" />
</form>

Namun, form yang berguna tentu perlu struktur yang lebih lengkap. Mari langsung buat versi yang realistis dan mudah dikembangkan.

Membuat Form Kontak Dasar

Berikut contoh form kontak yang memakai beberapa jenis elemen dasar: input, textarea, dan select. Contoh ini juga sudah memakai event submit dan validasi ringan.

<script>
  let name = '';
  let email = '';
  let category = 'general';
  let message = '';

  let errors = {};
  let submitted = false;

  function validateForm() {
    const newErrors = {};

    if (!name.trim()) {
      newErrors.name = 'Nama wajib diisi';
    }

    if (!email.trim()) {
      newErrors.email = 'Email wajib diisi';
    } else if (!/^\S+@\S+\.\S+$/.test(email)) {
      newErrors.email = 'Format email tidak valid';
    }

    if (!message.trim()) {
      newErrors.message = 'Pesan wajib diisi';
    } else if (message.trim().length < 10) {
      newErrors.message = 'Pesan minimal 10 karakter';
    }

    errors = newErrors;
    return Object.keys(newErrors).length === 0;
  }

  function handleSubmit() {
    submitted = false;

    if (!validateForm()) {
      return;
    }

    const payload = {
      name,
      email,
      category,
      message
    };

    console.log('Data form:', payload);

    submitted = true;

    name = '';
    email = '';
    category = 'general';
    message = '';
    errors = {};
  }
</script>

<form on:submit|preventDefault={handleSubmit}>
  <div>
    <label for="name">Nama</label>
    <input id="name" type="text" bind:value={name} placeholder="Nama lengkap" />
    {#if errors.name}
      <p style="color: red;">{errors.name}</p>
    {/if}
  </div>

  <div>
    <label for="email">Email</label>
    <input id="email" type="email" bind:value={email} placeholder="email@contoh.com" />
    {#if errors.email}
      <p style="color: red;">{errors.email}</p>
    {/if}
  </div>

  <div>
    <label for="category">Kategori</label>
    <select id="category" bind:value={category}>
      <option value="general">Pertanyaan Umum</option>
      <option value="support">Bantuan Teknis</option>
      <option value="business">Kerja Sama</option>
    </select>
  </div>

  <div>
    <label for="message">Pesan</label>
    <textarea
      id="message"
      rows="5"
      bind:value={message}
      placeholder="Tulis pesan Anda"
    ></textarea>
    {#if errors.message}
      <p style="color: red;">{errors.message}</p>
    {/if}
  </div>

  <button type="submit">Kirim</button>

  {#if submitted}
    <p style="color: green;">Form berhasil dikirim.</p>
  {/if}
</form>

Walau tampilannya masih sederhana, contoh di atas sudah mencakup alur penting dalam form SvelteKit:

  • State disimpan dalam variabel lokal komponen.
  • Nilai input diikat ke state memakai bind:value.
  • Submit form ditangani dengan event on:submit.
  • preventDefault mencegah browser melakukan submit biasa dan reload halaman.
  • Validasi ringan dilakukan sebelum data diproses lebih lanjut.

Memahami bind:value pada Input, Textarea, dan Select

Mengapa bind:value penting?

Di banyak framework frontend, kita perlu menulis event handler terpisah untuk setiap input, misalnya menangani onChange atau onInput, lalu menyalin nilainya ke state. Svelte menyederhanakan ini melalui bind:value. Dengan satu deklarasi, nilai elemen form dan variabel JavaScript akan tetap sinkron.

Contoh dasar:

<script>
  let name = '';
</script>

<input type="text" bind:value={name} />
<p>Nilai saat ini: {name}</p>

Saat pengguna mengetik, nilai name akan berubah otomatis. Jika variabel name Anda ubah dari script, isi input juga ikut berubah.

Contoh pada textarea

<script>
  let message = '';
</script>

<textarea bind:value={message}></textarea>

Untuk textarea, penggunaan bind:value sama seperti input biasa. Jangan campur cara lama seperti menaruh isi awal di antara tag <textarea> jika Anda sudah memakai binding. Lebih aman konsisten menggunakan variabel.

Contoh pada select

<script>
  let category = 'general';
</script>

<select bind:value={category}>
  <option value="general">Umum</option>
  <option value="support">Support</option>
</select>

Nilai option yang terpilih akan langsung masuk ke variabel category. Ini berguna untuk filter, form tambah data, atau pengelompokan jenis input.

Menangani Submit Form dengan Benar

Secara default, elemen <form> akan mengirim data ke server dan me-refresh halaman. Untuk interaksi client-side, kita biasanya ingin menangani submit sendiri, terutama saat validasi dilakukan di browser terlebih dahulu.

Di Svelte, pola yang umum adalah:

<form on:submit|preventDefault={handleSubmit}>

Modifier |preventDefault penting karena mencegah perilaku bawaan browser. Setelah itu, fungsi handleSubmit bisa memeriksa validasi, membentuk payload, memanggil API, atau menampilkan pesan sukses.

Contoh fungsi submit sederhana:

function handleSubmit() {
  if (!validateForm()) return;

  const payload = {
    name,
    email,
    category,
    message
  };

  console.log(payload);
}

Pola ini cocok untuk kebutuhan awal. Namun perlu dipahami bahwa validasi client tidak cukup untuk keamanan. Jika data benar-benar dipakai oleh server, Anda tetap harus melakukan validasi ulang di backend atau di action/server endpoint SvelteKit.

Validasi client berguna untuk pengalaman pengguna yang lebih baik, bukan sebagai lapisan keamanan utama.

Validasi Ringan di Sisi Client

Apa yang dimaksud validasi ringan?

Validasi ringan berarti pemeriksaan dasar seperti:

  • Field wajib tidak boleh kosong.
  • Format email tampak valid.
  • Panjang minimum pesan terpenuhi.
  • Option tertentu memang dipilih.

Jenis validasi ini cukup untuk membantu pengguna memperbaiki input sebelum data dikirim. Kita tidak membahas validasi kompleks lintas field atau integrasi schema validator agar fokus tetap pada dasar form Svelte.

Pola penyimpanan error

Pada contoh sebelumnya, error disimpan dalam objek:

let errors = {};

Kemudian diisi berdasarkan hasil pengecekan:

if (!name.trim()) {
  newErrors.name = 'Nama wajib diisi';
}

Pola ini sederhana dan mudah diperluas. Anda dapat menambahkan field baru tanpa mengubah struktur dasar validasi.

Menampilkan error di UI

Error ditampilkan secara kondisional memakai blok {#if ...}:

{#if errors.email}
  <p style="color: red;">{errors.email}</p>
{/if}

Ini lebih baik dibanding memakai alert() karena pesan error muncul tepat di dekat field terkait. Dalam aplikasi nyata, sebaiknya gunakan class CSS agar tampilan lebih rapi dan konsisten.

Versi yang Lebih Rapi dengan Styling Sederhana

Berikut contoh halaman yang lebih lengkap dan lebih enak dipakai. Kodenya tetap sederhana, tetapi sudah mendekati implementasi harian.

<script>
  let form = {
    name: '',
    email: '',
    category: 'general',
    message: ''
  };

  let errors = {};
  let submitted = false;

  function validateForm() {
    const newErrors = {};

    if (!form.name.trim()) {
      newErrors.name = 'Nama wajib diisi';
    }

    if (!form.email.trim()) {
      newErrors.email = 'Email wajib diisi';
    } else if (!/^\S+@\S+\.\S+$/.test(form.email)) {
      newErrors.email = 'Format email tidak valid';
    }

    if (!form.message.trim()) {
      newErrors.message = 'Pesan wajib diisi';
    } else if (form.message.trim().length < 10) {
      newErrors.message = 'Pesan minimal 10 karakter';
    }

    errors = newErrors;
    return Object.keys(newErrors).length === 0;
  }

  function handleSubmit() {
    submitted = false;

    if (!validateForm()) return;

    console.log('Form terkirim:', form);

    submitted = true;
    form = {
      name: '',
      email: '',
      category: 'general',
      message: ''
    };
    errors = {};
  }
</script>

<form class="contact-form" on:submit|preventDefault={handleSubmit}>
  <div class="field">
    <label for="name">Nama</label>
    <input id="name" type="text" bind:value={form.name} />
    {#if errors.name}<small class="error">{errors.name}</small>{/if}
  </div>

  <div class="field">
    <label for="email">Email</label>
    <input id="email" type="email" bind:value={form.email} />
    {#if errors.email}<small class="error">{errors.email}</small>{/if}
  </div>

  <div class="field">
    <label for="category">Kategori</label>
    <select id="category" bind:value={form.category}>
      <option value="general">Pertanyaan Umum</option>
      <option value="support">Bantuan Teknis</option>
      <option value="business">Kerja Sama</option>
    </select>
  </div>

  <div class="field">
    <label for="message">Pesan</label>
    <textarea id="message" rows="5" bind:value={form.message}></textarea>
    {#if errors.message}<small class="error">{errors.message}</small>{/if}
  </div>

  <button type="submit">Kirim</button>

  {#if submitted}
    <p class="success">Pesan berhasil dikirim.</p>
  {/if}
</form>

<style>
  .contact-form {
    max-width: 520px;
    display: grid;
    gap: 1rem;
  }

  .field {
    display: grid;
    gap: 0.4rem;
  }

  input, textarea, select {
    padding: 0.75rem;
    border: 1px solid #ccc;
    border-radius: 8px;
    font: inherit;
  }

  button {
    padding: 0.75rem 1rem;
    border: 0;
    border-radius: 8px;
    cursor: pointer;
  }

  .error {
    color: #b00020;
  }

  .success {
    color: #0a7a2f;
  }
</style>

Pada versi ini, state form disimpan dalam satu objek form. Ini lebih nyaman saat jumlah field bertambah. Anda juga lebih mudah mengirim seluruh payload ke API karena strukturnya sudah menyerupai data akhir.

Kesalahan Umum yang Sering Terjadi

Tidak memakai preventDefault

Jika form tiba-tiba me-refresh halaman saat tombol submit ditekan, biasanya penyebabnya adalah event submit tidak memakai |preventDefault. Akibatnya, browser menjalankan perilaku standar form.

Regex email terlalu ketat

Untuk validasi ringan, gunakan pemeriksaan yang sederhana saja. Regex email yang terlalu kompleks sering mempersulit pengguna tanpa manfaat besar di tahap client. Validasi final sebaiknya tetap dilakukan di server.

Lupa reset status sukses

Jika Anda menampilkan pesan sukses, jangan lupa mengatur ulang status seperti submitted = false saat submit berikutnya dimulai. Kalau tidak, UI bisa menampilkan status yang membingungkan.

Mencampur binding dan manipulasi DOM manual

Jika sudah memakai bind:value, hindari terlalu sering mengambil nilai langsung dari DOM dengan pendekatan manual. Ini membuat alur state sulit dilacak dan tidak memanfaatkan reaktivitas Svelte.

Debugging dan Pengembangan Lanjutan

Saat form tidak bekerja sesuai harapan, lakukan beberapa pemeriksaan berikut:

  • Pastikan variabel yang dibind memang dideklarasikan di <script>.
  • Periksa nama field dan key pada objek errors.
  • Gunakan console.log(form) di dalam handleSubmit untuk memastikan nilai terbaru benar-benar terbaca.
  • Jika memakai object binding seperti form.name, pastikan struktur object tidak berubah ke bentuk yang tidak sesuai.

Setelah memahami dasar ini, langkah berikutnya yang biasanya dibutuhkan adalah:

  • Mengirim data ke endpoint internal atau API eksternal dengan fetch.
  • Memindahkan validasi penting ke server.
  • Menambahkan status loading saat request berjalan.
  • Menangani error dari server dan menampilkannya ke pengguna.
  • Menggunakan form actions SvelteKit bila ingin alur yang lebih terintegrasi.

Penutup

Membuat form dasar di SvelteKit sebenarnya tidak rumit. Kunci utamanya ada pada pemahaman bahwa Svelte memungkinkan elemen form terhubung langsung ke state melalui bind:value. Dari sana, Anda bisa membangun alur yang bersih: ambil input, validasi ringan, proses submit, lalu tampilkan feedback ke pengguna.

Untuk kebutuhan sehari-hari, kombinasi input, textarea, select, bind:value, dan on:submit|preventDefault sudah cukup kuat sebagai fondasi. Yang penting, jangan berhenti di validasi client saja jika data akan diproses secara nyata. Gunakan validasi server sebagai lapisan utama, sementara validasi client membantu meningkatkan pengalaman pengguna.

Jika Anda sedang belajar SvelteKit, menguasai pola form dasar ini akan sangat membantu karena hampir semua aplikasi memerlukannya.