100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
JavaScript

Forms and Validation

Learn how to build validated forms in Vue using v-model, computed validity checks, and patterns for showing timely, non-annoying error feedback.

Working with Data & APIsIntermediate10 min readJul 9, 2026
Analogies

Forms and Validation

Vue's v-model handles the mechanical part of forms — keeping an input's displayed value in sync with a piece of reactive state — but validation is a separate concern layered on top: deciding whether the current values are acceptable, when to tell the user they aren't, and how to prevent submission of invalid data. A well-built form distinguishes between raw field values, per-field error messages, and overall form validity, and it times error display carefully so users aren't scolded for a field they haven't finished typing into yet.

🏏

Cricket analogy: v-model keeps a scorecard field in sync like an alert scorer jotting runs the instant they're called, but validation is separate: deciding whether a claimed six is actually valid, when to flag a scoring dispute, and how to block a corrupted card from being submitted to the umpires.

Binding Fields With v-model

For a multi-field form, it's common to hold all field values in a single reactive() object rather than one ref per field, since this keeps related fields grouped and makes it trivial to serialize the whole form for submission. Each input binds via v-model="formState.fieldName", and Vue keeps the object's property in sync with user input on every keystroke by default (or on blur/change if you use the .lazy modifier).

🏏

Cricket analogy: Holding all scorecard fields in one reactive() object — batsman, runs, overs, wickets — rather than separate refs keeps the whole innings grouped and trivial to submit as one record, with each field bound via v-model="scoreState.runs" and so on.

Deriving Validity With Computed Properties

Rather than manually calling a validate() function on every keystroke, express each field's validity as a computed() that derives from the field's current value — this keeps validation automatically in sync with the data and cache-friendly. The form's overall isValid can then be a computed that combines every individual field computed, so the submit button's disabled state and any 'you have errors' banner all stay consistent for free.

🏏

Cricket analogy: Instead of manually rechecking on every ball, express a batsman's not-out status as a computed() derived from wickets fallen; the innings' overall isValid computed then combines every field's computed, so the declaration button's state stays consistent automatically.

vue
<script setup>
import { reactive, computed, ref } from 'vue'

const form = reactive({ email: '', password: '' })
const touched = reactive({ email: false, password: false })

const emailError = computed(() => {
  if (!form.email) return 'Email is required'
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)) return 'Enter a valid email'
  return ''
})

const passwordError = computed(() => {
  if (form.password.length < 8) return 'Password must be at least 8 characters'
  return ''
})

const isFormValid = computed(() => !emailError.value && !passwordError.value)

function handleSubmit() {
  touched.email = true
  touched.password = true
  if (!isFormValid.value) return
  // submit form.email / form.password to the API
}
</script>

<template>
  <form @submit.prevent="handleSubmit">
    <input v-model="form.email" @blur="touched.email = true" type="email" />
    <p v-if="touched.email && emailError">{{ emailError }}</p>

    <input v-model="form.password" @blur="touched.password = true" type="password" />
    <p v-if="touched.password && passwordError">{{ passwordError }}</p>

    <button type="submit" :disabled="!isFormValid">Sign Up</button>
  </form>
</template>

Timing Error Messages With 'Touched' State

Showing 'Email is required' the instant the page loads, before a user has typed anything, feels aggressive and unhelpful. The common fix is a parallel touched flag per field, set to true on the field's blur event (or on first submit attempt), and templates only render the error when both the field is invalid AND has been touched. This gives users room to type without premature judgment while still catching errors before submission.

🏏

Cricket analogy: Flashing 'Invalid declaration' the moment the scorecard opens, before anyone's entered runs, feels aggressive; a touched flag set true on the field's blur (or first submit attempt) means the error only shows once the scorer has actually left that field.

For complex, multi-page, or deeply nested forms, dedicated libraries like VeeValidate or vuelidate provide schema-based validation (often paired with a schema library like Yup or Zod), built-in touched/dirty tracking, and integration with UI component libraries — worth adopting once hand-rolled computed validators start feeling repetitive across many forms.

A subtle gotcha: if form is a reactive() object and you destructure a field out of it (const { email } = form), you lose the reactive connection to that property, just as with any reactive object. Always access fields via form.email in the template or wrap destructured fields with toRefs(form) if you need standalone refs.

  • Group related form fields in a single reactive() object bound via v-model on each input.
  • Express per-field validity as computed() properties so validation stays automatically in sync with input.
  • Combine per-field computeds into an overall isFormValid computed to drive the submit button and summary banners.
  • Use a touched flag per field (set on blur or submit) to avoid showing errors before the user has interacted with a field.
  • For complex forms, schema-based libraries like VeeValidate reduce repetitive hand-rolled validation logic.
  • Destructuring fields out of a reactive() form object loses reactivity — access via the object or use toRefs().

Practice what you learned

Was this page helpful?

Topics covered

#JavaScript#VueJsStudyNotes#WebDevelopment#FormsAndValidation#Forms#Validation#Binding#Fields#StudyNotes#SkillVeris