Getting started
vee-validate is built from the ground up with the composition API through a collection of functions, mainly the useField
and useForm
functions. Internally the <Form />
and <Field />
components actually use the composition functions under the hood.
Meaning you can create your own custom input and form components and they will be treated the same as <Form />
and <Field />
components. You can mix them together and use a Form
component with any other custom component that uses useField
and vice versa.
vee-validate supports synchronous and asynchronous validation and allows defining rules on the field level or the form level using validation schemas. vee-validate has first-class support for:
- yup through
@vee-validate/yup
package. - zod through
@vee-validate/zod
package. - valibot through
@vee-validate/valibot
package. - Global validators (Laravel-like syntax) through
@vee-validate/rules
.
vee-validate has historically been a declarative validation library, and while the composition API changes things a bit, it still follows the same mindset of declarative validation. vee-validate optimizes for building fields and forms, not values.
When to use composition API
While vee-validate offers both declarative components and composition functions to supercharge your forms, it is always up to you to decide which one to use. However, the composition API is easier to integrate and more flexible. You can build custom components with it or integrate it with any UI library. It is generally recommended to use the composition API.
On this page, you will learn how to declare forms and how to hook your elements and components into vee-validate forms and achieve value tracking, validation, and more.
Declaring Forms
Form context
You can declare forms with the useForm
function exported from the vee-validate
core package. This is a composition API function that marks the current component as a form.
vue<script setup>
import { useForm } from 'vee-validate';
// Creates a form context
// This component now acts as a form
// Usually you will destruct the form context to get what you need
const { values } = useForm();
</script>
<template>
<pre>{{ values }}</pre>
</template>
Calling useForm
creates a form context in the component and provides it for any child component that injects it. This means you should stick to calling useForm
once in a component.
Creating a form context does a few things:
- Acts as a value collector for all the fields you will declare as child components.
- Validates the fields and aggregates the errors.
- Aggregates the validity, touched, and dirty states of all the fields.
Field binds
With useForm
declared, you are now ready to integrate the form with your elements and components. vee-validate is agnostic to the UI you are using.
You will learn how to associate your components and elements with the form and how to get value collection, validation, and error messages working.
HTML Inputs
useForm
provides a function called defineField
. This function accepts a field path and returns a value model and an object containing the bindings for the input element. The field path is a string that represents the path to the field in the form context.
For example, if you have a field called email
in the form context, the field path will be email
.
tsconst { defineField } = useForm();
const [email, emailAttrs] = defineField('email');
Note that defineField
generates a pair of values, the first one is the value model and the second one is the attributes/props object. The props object contain attributes or event listeners that are useful to have on the input control or component which enables custom validation triggers and more.
Here is a basic example of how to use defineField
with a simple input element:
Notice that as you type in the input, the values
are automatically updated with the value changes.
Let’s quickly add a validation schema on the form to see some errors on the form. We will be using yup
throughout the examples, but you can use zod
or any other supported validation library you want.
To add a yup
schema or any kind of form schema, you pass it to the validationSchema
option when calling useForm
. Naturally, form schemas are almost always an object
or a shape
schema.
tsimport * as yup from 'yup';
const schema = yup.object({
email: yup.string().required().email(),
});
const { defineField } = useForm({
validationSchema: schema,
});
const [email, emailAttrs] = defineField('email');
Here is a full running example:
Notice as you type into the input, the validation is then triggered and the errors are populated. By default defineField
optimizes for aggressive validation, meaning the validation will be triggered whenever the model changes.
You can change that behavior. For example, you can make it “lazy” by passing a configuration to defineField
to disable validation on model updates with validateOnModelUpdate
config.
Now as you type in the field, the input is not immediately validated. You can do more with dynamic configurations.
Components
Similarly to HTML inputs, you can achieve the same results with the same defineField
function.
Following the previous examples we can achieve value tracking like this:
As you type into the input, notice that the values
are being updated.
Let’s add validation to the previous example:
Notice that for components, validations are executed immediately. This is intended because component implementations are not standardized across the Vue ecosystem as there is no guarantee it will emit the same range of events as the native HTML elements. However, you can customize the validation trigger if you know the components you are using are emitting the right events to support the behavior.
Mapping attributes and props
But aside from the attributes and listeners that vee-validate adds to those binding objects, you can also map the attributes and props of the in. This is useful when you want to:
- Map the attributes/props to a different name.
- Pass new attributes/props to the component/element based on the field state.
You can use props
to include any additional props or attributes you need to add to the component/element.
tsconst { defineField } = useForm({
// ...
});
const [email, emailProps] = defineField('email', { props: state => ({ error: state.errors[0] }) });
In the following example, we have a component that accepts an error
string prop and shows that message if it is not empty. This is common in many UI libraries as they try not to lock you into a specific validation library.
tip
The state
object contains a lot of useful information about the field, it is fully typed so you can explore it with your IDE or visit the source reference for more information.
Notice that form
also gives you access to errors
so you can reference them anywhere in the component.
Dynamic configuration
Instead of passing a static configuration object defineField
, you could pass a function that returns different configuration values. This is useful when you want the configuration to be dynamic based on the field state.
Here is an example that shows how to make the validation behavior “eager”. Meaning if the field does not have any errors then it will only validate on change
.
But once it is invalid, it validates on each input event, making it “eager” for success.
Form Schema
As you have seen in the previous examples, the useForm
function accepts a validationSchema
that is used to validate the form. We’ve been using yup to define the schema however you can use zod
or any 3rd-party validators.
Validating with Yup
You can pass yup schemas directly as you’ve seen previously, but it is recommended that you use the @vee-validate/yup
package. It will enable better support of yup features and unlock full typescript support for your schemas.
sh# with npm
npm i @vee-validate/yup
# with pnpm
pnpm add @vee-validate/yup
# with yarn
yarn add @vee-validate/yup
The package exposes a toTypedSchema
function that you can use to wrap your yup schemas, this allows vee-validate to infer form input and output types. More on that here.
tsimport { useForm } from 'vee-validate';
import { toTypedSchema } from '@vee-validate/yup';
import * as yup from 'yup';
// Creates a typed schema for vee-validate
const schema = toTypedSchema(
yup.object({
email: yup.string().required().email(),
}),
);
const { errors, values } = useForm({
validationSchema: schema,
});
Yup Schema Optimizations
There are a couple of optimization caveats when it comes to using yup
schemas to validate your forms, be sure to check the best practices guide.
Validating with Zod
You can use Zod in a very similar manner to how we’ve been using yup in the past examples. However, you will need to add @vee-validate/zod
to your dependencies.
sh# with npm
npm i @vee-validate/zod
# with pnpm
pnpm add @vee-validate/zod
# with yarn
yarn add @vee-validate/zod
Then you can wrap your zod schemas with toTypedSchema
function which allows vee-validate to infer form input and output types. More on that here.
tsimport { useForm } from 'vee-validate';
import { toTypedSchema } from '@vee-validate/zod';
import { z } from 'zod';
// Creates a typed schema for vee-validate
const schema = toTypedSchema(
z.object({
email: z.string().nonempty().email(),
}),
);
const { errors, values } = useForm({
validationSchema: schema,
});
Here is a full example using zod with useForm
:
refine/superRefine
There is a known issue with Zod’s refine
and superRefine
not executing whenever some object keys are missing which is common with forms. This is not an issue with vee-validate as it is a design choice in Zod at the moment. Refer to this issue for explanations and further reading.
Validating with Valibot
You can also use Valibot which is is a schema library with bundle size, type safety and developer experience in mind. It is a great alternative to Yup and Zod if bundle size is a concern. You will need to use the @vee-validate/valibot
package to use valibot schemas.
sh# with npm
npm i @vee-validate/valibot
# with pnpm
pnpm add @vee-validate/valibot
# with yarn
yarn add @vee-validate/valibot
Then you can wrap your Valibot schemas with toTypedSchema
function which allows vee-validate to infer form input and output types. More on that here.
tsimport { useForm } from 'vee-validate';
import * as v from 'valibot';
import { toTypedSchema } from '@vee-validate/valibot';
// Creates a typed schema for vee-validate
const schema = toTypedSchema(
v.object({
email: v.pipe(v.string(), v.email('Invalid email'), v.nonEmpty('required')),
}),
);
const { errors, values } = useForm({
validationSchema: schema,
});
Here is a full example using valibot with useForm
:
Other validation providers and options
Global Rules
Another option is using @vee-validate/rules
which have been historically bundled with past versions of vee-validate. It includes rules that can be defined globally and then used anywhere using Laravel-like string expressions.
You can refer to the full guide on global rules here.
Validating with functions
Another option is to just use any 3rd party validation tool you prefer, something like validator.js
. Here is a quick example:
Or you could use any custom function.
tip
Both zod
and yup
are very good at defining schemas especially nested values so it is recommended that you use either. As an added bonus, you get full typescript support with either of them. You can refer to the Typed schemas guide to learn more about how to maximize the type safety of your schemas.
Dynamic Form Schemas
There are a few ways you can create dynamic schemas (reactive) where it changes the validation rules based on some state. The first way to do that is with computed
.
When the validation schema value changes, only the fields that were validated at least once will be re-validated, the other fields won’t be validated to avoid aggressive validation behavior.
There are other ways depending on which validation library you are using. For example, with yup
you can achieve the same with yup.lazy
or zod.lazy
:
Next Step
Now that you've learned how to use forms to define fields, collect their values and validate them. Next, you will learn how to handle submissions and implement advanced patterns with forms.