vee-validate and vuelidate are two of the most prominent validation libraries for Vue.js, but they take fundamentally different approaches to solving form state and validation logic. vee-validate (v4+) is built specifically for Vue 3, offering both a component-based API and a composition API that tightly integrates with Vue's reactivity system to handle complex form scenarios, error collection, and submission flows. vuelidate started as a lightweight, model-driven validator for Vue 2, and its Vue 3 successor (@vuelidate/core) maintains a focus on simplicity and external state management, allowing validation rules to exist outside of the component template. While both aim to reduce boilerplate, vee-validate leans towards an all-in-one form solution, whereas vuelidate acts more as a validation state layer that you wire up manually.
Choosing the right validation library can make or break the developer experience in a Vue application. Both vee-validate and vuelidate solve the same problem — ensuring user input meets specific criteria — but they differ significantly in architecture, API design, and scope. Let's break down how they handle real-world engineering challenges.
vee-validate offers a dual approach. You can use dedicated components like <Form> and <Field> that abstract away the wiring, or use the useForm and useField composables for full control. This makes it flexible for both rapid prototyping and complex custom inputs.
// vee-validate: Component-based approach
<template>
<Form @submit="onSubmit">
<Field name="email" rules="required|email" />
<ErrorMessage name="email" />
<button type="submit">Submit</button>
</Form>
</template>
vuelidate is purely logic-based. It provides a useVuelidate composable that returns a reactive validation state object. You are responsible for binding the $model, $error, and $touch properties to your template inputs manually.
// vuelidate: Composition API approach
<script setup>
import { useVuelidate } from '@vuelidate/core'
import { required, email } from '@vuelidate/validators'
const state = reactive({ email: '' })
const rules = { email: { required, email } }
const v$ = useVuelidate(rules, state)
</script>
<template>
<input v-model="state.email" @blur="v$.email.$touch" />
<span v-if="v$.email.$error">Invalid email</span>
</template>
vee-validate uses a string-based syntax for common rules (like 'required|email') or an object for more complex logic. It comes with a built-in set of common rules, but you can easily register global custom rules.
// vee-validate: String or Object rules
<Field name="password" rules="required|min:6" />
// Or with object for params
<Field name="age" :rules="{ required: true, min_value: 18 }" />
vuelidate requires you to import validators from a separate package (@vuelidate/validators) and construct a rules object that mirrors your state structure. This keeps validation logic explicit but adds more imports.
// vuelidate: Imported validators
import { required, minLength } from '@vuelidate/validators'
const rules = {
password: { required, minLength: minLength(6) }
}
vee-validate has built-in components for displaying errors, such as <ErrorMessage>. It automatically tracks which field is touched and displays messages only when relevant, reducing boilerplate in your templates.
// vee-validate: Built-in error component
<Field name="username" rules="required" />
<ErrorMessage name="username" class="error-text" />
vuelidate does not provide UI components. You must write your own logic to check $error and $dirty flags to decide when to show messages. This gives you full control but requires more repetitive code.
// vuelidate: Manual error rendering
<input v-model="state.username" @blur="v$.username.$touch" />
<span v-if="v$.username.$dirty && v$.username.$error" class="error-text">
Username is required
</span>
vee-validate manages the entire form lifecycle. The <Form> component handles the submit event, prevents submission if invalid, and aggregates all field values into a single object passed to your handler.
// vee-validate: Handled submission
<Form @submit="handleSubmit">
<Field name="email" />
<button type="submit">Send</button>
</Form>
<script setup>
const handleSubmit = (values) => {
console.log(values.email) // Access all values here
}
</script>
vuelidate only validates state. You must handle the form submit event yourself, call $validate() to trigger checks, and manually check $invalid before proceeding with your API call.
// vuelidate: Manual submission check
<script setup>
const submitForm = async () => {
const isCorrect = await v$.value.$validate()
if (!isCorrect) return
await api.submit(state)
}
</script>
vee-validate supports async rules out of the box. You can define a rule that returns a promise, and the field will handle the pending state automatically.
// vee-validate: Async rule
const checkUnique = async (value) => {
const { data } = await api.checkUsername(value)
return data.available || 'Username taken'
}
<Field name="username" :rules="checkUnique" />
vuelidate also supports async validators, but you need to wrap them correctly and manage the $pending flag in your UI manually to show loading spinners.
// vuelidate: Async validator
const unique = (value) => {
return api.checkUsername(value).then(res => res.available)
}
const rules = { username: { unique } }
// In template
<span v-if="v$.username.$pending">Checking...</span>
Despite their differences, both libraries share core goals and capabilities.
// Both use reactive state
const state = reactive({ field: '' }) // Works in both ecosystems
// vee-validate custom rule
defineRule('odd', (value) => value % 2 !== 0)
// vuelidate custom rule
const odd = (value) => value % 2 !== 0
// vee-validate: referencing other fields
<Field name="confirm" rules="confirmed:password" />
// vuelidate: accessing state in rule
const sameAs = (other) => (value) => value === state[other]
| Feature | vee-validate | vuelidate |
|---|---|---|
| Primary API | Components (<Field>) + Composables | Composables (useVuelidate) only |
| Error UI | Built-in <ErrorMessage> component | Manual implementation required |
| Form Submission | Handled by <Form> component | Manual $validate() call |
| Rule Syntax | Strings ('required') or Objects | Imported Validator Functions |
| Vue 3 Support | Native (v4+) | Via @vuelidate/core |
| Maintenance | Active and frequent updates | Slower pace, maintenance mode for Vue 2 |
vee-validate is like a full-service form manager 🧰. It handles the validation, the error display, and the submission logic. It is ideal for dashboards, settings pages, and any form where you want to minimize boilerplate and ensure consistency across your UI.
vuelidate is like a precision validation tool 🔍. It validates your data model and stays out of your UI layer. It is suitable for simpler forms or teams that prefer to build their own form components from scratch without opinionated wrappers.
Final Thought: For most modern Vue 3 applications, vee-validate offers a more complete and sustainable solution. However, if you need lightweight validation on a data model without form UI constraints, vuelidate remains a viable option — provided you are using the Vue 3 compatible package.
Choose vee-validate if you are building complex forms in Vue 3 that require robust error handling, custom input components, or server-side error integration. It is the better fit for projects that need a complete form management solution, including submission handling, dirty state tracking, and built-in support for UI frameworks like Vuetify or Tailwind. Its active maintenance and Vue 3-first architecture make it a safer long-term bet for new enterprise applications.
Choose vuelidate (specifically @vuelidate/core for Vue 3) if you prefer a lightweight, model-centric approach where validation logic is decoupled from your UI components. It is suitable for simpler forms or legacy Vue 2 projects where you want minimal overhead and do not need advanced form submission features. However, be aware that development velocity has slowed compared to vee-validate, so evaluate if the community support meets your project's risk tolerance.
Painless Vue forms
# Install with yarn
yarn add vee-validate
# Install with npm
npm install vee-validate --save
The main v4 version supports Vue 3.x only, for previous versions of Vue, check the following the table
| vue Version | vee-validate version | Documentation Link |
|---|---|---|
2.x | 2.x or 3.x | v2 or v3 |
3.x | 4.x | v4 |
vee-validate offers two styles to integrate form validation into your Vue.js apps.
The fastest way to create a form and manage its validation, behavior, and values is with the composition API.
Create your form with useForm and then use defineField to create your field model and props/attributes and handleSubmit to use the values and send them to an API.
<script setup>
import { useForm } from 'vee-validate';
// Validation, or use `yup` or `zod`
function required(value) {
return value ? true : 'This field is required';
}
// Create the form
const { defineField, handleSubmit, errors } = useForm({
validationSchema: {
field: required,
},
});
// Define fields
const [field, fieldProps] = defineField('field');
// Submit handler
const onSubmit = handleSubmit(values => {
// Submit to API
console.log(values);
});
</script>
<template>
<form @submit="onSubmit">
<input v-model="field" v-bind="fieldProps" />
<span>{{ errors.field }}</span>
<button>Submit</button>
</form>
</template>
You can do so much more than this, for more info check the composition API documentation.
Higher-order components can also be used to build forms. Register the Field and Form components and create a simple required validator:
<script setup>
import { Field, Form } from 'vee-validate';
// Validation, or use `yup` or `zod`
function required(value) {
return value ? true : 'This field is required';
}
// Submit handler
function onSubmit(values) {
// Submit to API
console.log(values);
}
</script>
<template>
<Form v-slot="{ errors }" @submit="onSubmit">
<Field name="field" :rules="required" />
<span>{{ errors.field }}</span>
<button>Submit</button>
</Form>
</template>
The Field component renders an input of type text by default but you can control that
Read the documentation and demos.
You are welcome to contribute to this project, but before you do, please make sure you read the contribution guide.
Here we honor past contributors and sponsors who have been a major part on this project.