Forms are deceptively hard. The binding is easy in Vue, but validation — when to run it, what to show, how to keep it from nagging — is where forms become frustrating or pleasant. Vue 3 gives you the primitives to build good forms by hand, and an ecosystem of libraries for when the complexity grows. Knowing the patterns matters more than the tool.
Binding with v-model
v-model gives you two-way binding between form inputs and reactive state with minimal code, and it works on custom components too, so you can build reusable input components that participate in v-model. This part is genuinely simple; the state behind the form is just refs, and the inputs stay in sync automatically. The complexity is never the binding — it is everything around validation.
Validate at the right time
Validation timing is what makes a form feel considerate or annoying. Validating every keystroke from the start flags errors before the user has finished typing, which is nagging. A good default: validate a field on blur (when the user leaves it) and, once it has shown an error, re-validate on input so they see the error clear as they fix it. Always validate everything on submit as the backstop. Getting this rhythm right is most of what makes a form pleasant.
const email = ref('')
const touched = ref(false)
const error = computed(() => {
if (!touched.value) return ''
if (!email.value) return 'Email is required'
if (!/^[^@]+@[^@]+\.[^@]+$/.test(email.value)) return 'Enter a valid email'
return ''
})
// @blur="touched = true" -> error appears only after the field is left
Show clear errors
- Place the error next to the field it belongs to, not in a distant summary.
- Write specific messages — 'Enter a valid email', not 'Invalid input'.
- Never disable the submit button silently; let users submit and show them what to fix.
- On submit failure, focus the first invalid field so keyboard and screen-reader users find it.
Hand-rolling is fine for simple forms, but complex ones — many fields, cross-field rules, dynamic sections, schema validation — are where a library like VeeValidate or a schema tool (Zod, Yup) pays off, handling the tedious state and letting you declare rules. Start hand-rolled, adopt a library when the validation logic outgrows a few computed properties.
Vue 3 forms are easy to bind with v-model and hard to validate well — the craft is in timing (blur then input, always on submit), clear per-field errors, and accessible failure handling. Hand-roll the simple ones with refs and computed validators, and adopt a form library once cross-field rules and dynamic fields make the manual approach unwieldy. The library is a convenience; the patterns are what make the form good.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.