Prompt
How do I validate complex forms in Vue.js?
Latest observation
Aug 27, 2026
OpenAI APIWeb search: off
Validating complex forms in Vue.js usually means handling:
- field-level rules (required, min length, email, etc.)
- cross-field rules (e.g. password confirmation, date ranges)
- async checks (e.g. username availability)
- dynamic fields (repeatable sections, conditional inputs)
A good approach is to choose between:
- Manual validation for small/medium forms
- A validation library for larger, complex forms
1) Manual validation in Vue
For simple cases, you can keep validation state in your component.
<template>
<form @submit.prevent="submitForm">
<div>
<label>Email</label>
<input v-model="form.email" @blur="validateEmail" />
<p v-if="errors.email">{{ errors.email }}</p>
</div>
<div>
<label>Password</label>
<input type="password" v-model="form.password" @blur="validatePassword" />
<p v-if="errors.password">{{ errors.password }}</p>
</div>
<div>
<label>Confirm Password</label>
<input type="password" v-model="form.confirmPassword" @blur="validateConfirmPassword" />
<p v-if="errors.confirmPassword">{{ errors.confirmPassword }}</p>
</div>
<button type="submit">Submit</button>
</form>
</template>
<script setup>
import { reactive } from 'vue'
const form = reactive({
email: '',
password: '',
confirmPassword: ''
})
const errors = reactive({
email: '',
password: '',
confirmPassword: ''
})
function validateEmail() {
errors.email = !form.email
? 'Email is required'
: !/^\S+@\S+\.\S+$/.test(form.email)
? 'Enter a valid email'
: ''
}
function validatePassword() {
errors.password = form.password.length < 8
? 'Password must be at least 8 characters'
: ''
}
function validateConfirmPassword() {
errors.confirmPassword = form.confirmPassword !== form.password
? 'Passwords do not match'
: ''
}
function validateForm() {
validateEmail()
validatePassword()
validateConfirmPassword()
return !errors.email && !errors.password && !errors.confirmPassword
}
function submitForm() {
if (!validateForm()) return
// submit data
console.log('Submitted', form)
}
</script>
2) Use a validation library for complex forms
For larger forms, libraries make life easier. The most popular choices are:
- VeeValidate — very Vue-friendly, good for schema-based and field-based validation
- Yup or Zod — schema validators often used with VeeValidate or custom setups
- FormKit — full form framework with validation built in
Example with VeeValidate + Yup
npm install vee-validate yup
<template>
<Form :validation-schema="schema" @submit="onSubmit">
<div>
<Field name="email" as="input" type="email" />
<ErrorMessage name="email" />
</div>
<div>
<Field name="password" as="input" type="password" />
<ErrorMessage name="password" />
</div>
<div>
<Field name="confirmPassword" as="input" type="password" />
<ErrorMessage name="confirmPassword" />
</div>
<button type="submit">Submit</button>
</Form>
</template>
<script setup>
import { Form, Field, ErrorMessage } from 'vee-validate'
import * as yup from 'yup'
const schema = yup.object({
email: yup.string().required('Email is required').email('Invalid email'),
password: yup.string().required('Password is required').min(8, 'Minimum 8 characters'),
confirmPassword: yup
.string()
.required('Confirm your password')
.oneOf([yup.ref('password')], 'Passwords must match')
})
function onSubmit(values) {
console.log(values)
}
</script>
3) Cross-field validation
For cases like “start date must be before end date,” validate against the whole form, not just one field.
Example idea:
if (form.startDate && form.endDate && form.startDate > form.endDate) {
errors.endDate = 'End date must be after start date'
}
With a library, this is usually handled with schema refs or custom tests.
4) Async validation
If you need to check something against the server:
async function validateUsername() {
if (!form.username) {
errors.username = 'Username is required'
return
}
const res = await fetch(`/api/users/check?username=${form.username}`)
const data = await res.json()
errors.username = data.available ? '' : 'Username is already taken'
}
Tip: debounce async validation so you don’t hit the server on every keystroke.
5) Best practices
- Validate on blur for better UX, and on submit as a final check
- Keep validation logic separate from rendering when possible
- Show errors near the relevant field
- Disable submit until required fields are valid, if appropriate
- Use a library when the form becomes large or dynamic
- For nested/dynamic arrays, prefer libraries like VeeValidate or FormKit
Recommendation
- Small form: manual validation is fine
- Complex form: use VeeValidate + Yup/Zod
- Very dynamic enterprise form: consider FormKit
If you want, I can give you:
- a Vue 3 Composition API validation example,
- a VeeValidate + Zod example, or
- a dynamic repeatable form example.