Episode ini membahas pengelolaan form di Vue: two-way binding dengan v-model pada berbagai jenis input, pattern validasi dengan VeeValidate dan Vuelidate, dynamic forms dengan custom validation rules, serta accessibility form dan umpan balik untuk pengguna.

Form adalah jembatan antara aplikasi dan pengguna: tempat kalian mengumpulkan data, memvalidasi input, dan menampilkan kesalahan saat data tidak sesuai. Form yang buruk membuat pengguna frustrasi dan data kotor masuk ke backend.
Episode 11 membahas pengelolaan form di Vue secara menyeluruh: pengikatan input dengan v-model, pattern validasi memakai VeeValidate dan alternatifnya Vuelidate, dynamic forms dengan custom validation rules, serta accessibility dan umpan balik agar form nyaman bagi semua orang.
v-model bekerja di hampir semua elemen form: teks, checkbox, radio, dan select. Vue menangani perbedaan nilai dan event di balik layar:
<script setup>
import { ref } from "vue";
const nama = ref("");
const aktif = ref(false);
const paket = ref("pro");
</script>
<template>
<input v-model="nama" />
<input type="checkbox" v-model="aktif" />
<select v-model="paket">
<option value="gratis">Gratis</option>
<option value="pro">Pro</option>
</select>
</template>v-model="aktif" pada checkbox menghasilkan boolean, sedangkan v-model="paket" pada select mengikat nilai option yang terpilih. Satu direktif, banyak jenis input ditangani otomatis.
Untuk form besar, kumpulkan state dalam satu object reactive dan tangani dengan @submit.prevent agar halaman tidak reload.
VeeValidate adalah library validasi paling populer di ekosistem Vue. Pasang bersama schema validator Yup:
npm install vee-validate yupVeeValidate memakai komposable useField dan useForm yang menyatu dengan composition API:
<script setup>
import { useField, useForm } from "vee-validate";
import * as yup from "yup";
const { handleSubmit } = useForm({
validationSchema: yup.object({
email: yup.string().email("Email tidak valid").required("Email wajib"),
password: yup.string().min(6, "Minimal 6 karakter").required(),
}),
});
const { value: email, errorMessage: emailError } = useField("email");
const { value: password, errorMessage: passError } = useField("password");
const onKirim = handleSubmit((nilai) => {
console.log("valid:", nilai);
});
</script>
<template>
<form @submit="onKirim">
<input v-model="email" />
<span>{{ emailError }}</span>
<input v-model="password" type="password" />
<span>{{ passError }}</span>
<button>Kirim</button>
</form>
</template>validationSchema didefinisikan sekali dengan Yup, lalu useField("email") menghubungkan field ke schema tersebut. errorMessage berisi pesan error siap tampil.
Rules bawaan tidak selalu cukup. Buat rule sendiri dengan fungsi validator:
import * as yup from "yup";
const schema = yup.object({
username: yup
.string()
.test("unik", "Username sudah dipakai", async (nilai) => {
const res = await fetch(`/api/cek-username?nama=${nilai}`);
const { tersedia } = await res.json();
return tersedia;
}),
});yup.string().test(...) menerima nama rule, pesan error, dan fungsi validasi. Karena bisa async, pengecekan ke server seperti cek username berjalan mulus.
Beberapa form membiarkan pengguna menambah baris, misalnya daftar item pesanan:
<script setup>
import { useFieldArray } from "vee-validate";
const { fields, push, remove } = useFieldArray("items");
</script>
<template>
<div v-for="(field, index) in fields" :key="field.key">
<input v-model="field.value.nama" />
<input v-model="field.value.qty" />
<button @click="remove(index)">Hapus</button>
</div>
<button @click="push({ nama: "", qty: 1 })">Tambah baris</button>
</template>useFieldArray("items") mengelola array field dengan push untuk menambah dan remove untuk menghapus. Setiap baris mendapat key unik agar Vue merender dengan benar.
Vuelidate memasangkan rules langsung ke state reactive dan bisa dipasang dengan npm install @vuelidate/core @vuelidate/validators. Error diakses lewat $errors dan $error menandai field yang gagal validasi.
Form yang aksesibel memberitahu pembaca layar tentang error dan memandu fokus:
<template>
<label for="email">Email</label>
<input
id="email"
type="email"
aria-describedby="email-hint"
:aria-invalid="!!emailError"
/>
<p id="email-hint">{{ emailError }}</p>
</template>aria-describedby="email-hint" menghubungkan input dengan pesan bantuan, dan :aria-invalid="!!emailError" menandai field yang gagal validasi.
Aturan praktis untuk umpan balik form: tampilkan error dekat field yang bermasalah, gunakan teks yang bisa dibaca manusia, jangan hanya warna merah sebagai satu-satunya indikator, dan validasi saat field kehilangan fokus, bukan saat setiap ketikan.
Episode 11 membuat kalian mengelola form dengan percaya diri: v-model untuk semua jenis input, VeeValidate dengan Yup untuk validasi berbasis schema, Vuelidate sebagai alternatif ringan, dynamic forms dengan useFieldArray, serta accessibility dan umpan balik yang memandu pengguna.
Inti yang harus dibawa pulang:
v-model menangani semua jenis input form..test() bisa mengecek ke server.useFieldArray untuk dynamic forms.Di episode 12 selanjutnya kita membahas authentication dan authorization — pattern autentikasi client-side di Vue, penyimpanan token dan refresh flow, route guards untuk halaman terproteksi, serta role-based access control yang membatasi UI sesuai hak akses pengguna.