Schemas, Fields, Errors — Out of the Box
VeeValidate — Full Form Library
VeeValidate handles real-world forms — schemas, async validation, field arrays, and scoped errors — without you writing the plumbing.
What you'll learn
- Install vee-validate plus yup or zod
- Use useForm with a validationSchema
- Surface errors per field
VeeValidate is the de facto form library for Vue. It supports schema validation (Yup or Zod), async rules, and gives you a useForm composable that tracks every flag you care about.
Install
npm install vee-validate yup @vee-validate/yup useForm With a Schema
<script setup lang="ts">
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/yup'
import * as yup from 'yup'
const schema = toTypedSchema(
yup.object({
email: yup.string().email().required(),
password: yup.string().min(8).required(),
})
)
const { defineField, handleSubmit, errors } = useForm({ validationSchema: schema })
const [email, emailAttrs] = defineField('email')
const [password, passwordAttrs] = defineField('password')
const onSubmit = handleSubmit(values => {
console.log('submit', values)
})
</script>
<template>
<form @submit="onSubmit">
<input v-model="email" v-bind="emailAttrs" />
<p v-if="errors.email">{{ errors.email }}</p>
<input v-model="password" type="password" v-bind="passwordAttrs" />
<p v-if="errors.password">{{ errors.password }}</p>
<button type="submit">Sign in</button>
</form>
</template> The schema drives both validation and TypeScript types — values inside handleSubmit are fully typed.
Form Components
There is also a higher-level API using Form, Field, and ErrorMessage components:
<Form :validation-schema="schema" @submit="onSubmit">
<Field name="email" type="email" />
<ErrorMessage name="email" />
<Field name="password" type="password" />
<ErrorMessage name="password" />
<button type="submit">Sign in</button>
</Form> Async Validation
Rules can be async — useful for “is this username available” checks:
const schema = yup.object({
username: yup.string().test('unique', 'Taken', async v => {
if (!v) return true
const res = await fetch(`/api/check?u=${v}`)
return res.status === 200
}),
}) VeeValidate debounces validation, tracks isValidating per field, and pauses submit while async rules run.
Zod + Vue →