---
layout: ../../../layouts/PageLayout.astro
title: Getting started
description: Field-level and form-level validation and validation behavior and error messages with composition API
order: 1
next:
  path: guide/composition-api/handling-forms
  title: Handling forms
  description: Submissions, resets and form state
  intro: |
    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.
---

import DocTip from '@/components/DocTip.vue';
import DocBadge from '@/components/DocBadge.vue';
import LiveExample from '@/components/LiveExample.vue';
import Repl from '@/components/MdxRepl.vue';

# 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:

- [Standard Schema](https://standardschema.dev/) libraries like zod, valibot, yup and more.
- [Global validators](/guide/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`.

```ts
const { 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:

<Repl files={{ 'App.vue': 'CompositionInputBindsBasic01' }} client:visible />

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.

```ts{1,3-5,8}
import * 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:

<Repl files={{ 'App.vue': 'CompositionInputBindsBasic02' }} client:visible />

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.

<Repl files={{ 'App.vue': 'CompositionInputBindsBasic03' }} client:visible />

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:

<Repl files={{ 'App.vue': 'CompositionComponentBindsBasic01', 'CustomInput.vue': 'CustomInputBasic' }} client:visible />

As you type into the input, notice that the `values` are being updated.

Let's add validation to the previous example:

<Repl files={{ 'App.vue': 'CompositionComponentBindsBasic02', 'CustomInput.vue': 'CustomInputBasic' }} client:visible />

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.

<Repl files={{ 'App.vue': 'CompositionComponentBindsBasic03', 'CustomInput.vue': 'CustomInputBasic' }} client:visible />

### 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.

```ts
const { 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.

<Repl
  files={{ 'App.vue': 'CompositionComponentBindsBasic04', 'CustomInput.vue': 'CustomInputBasicError' }}
  client:only
/>

<DocTip>

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](https://github.com/logaretm/vee-validate/blob/main/packages/vee-validate/src/types/forms.ts#L255-L258) for more information.

</DocTip>

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.

<Repl files={{ 'App.vue': 'CompositionComponentBindsBasic05' }} client:visible />

## 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.

<Repl files={{ 'App.vue': 'CompositionYupBasic' }} client:visible />

### Validating with Zod

You can use [Zod](https://github.com/colinhacks/zod) in a very similar manner to how we've been using yup in the past examples.

```ts
import { useForm } from 'vee-validate';
import { z } from 'zod';

const schema = z.object({
  email: z.string().nonempty().email(),
});

const { errors, values } = useForm({
  validationSchema: schema,
});
```

Here is a full example using zod with `useForm`:

<Repl files={{ 'App.vue': 'CompositionZodBasic' }} client:visible />

<DocTip title="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](https://github.com/logaretm/vee-validate/issues/4338) for explanations and further reading.

</DocTip>

### Validating with Valibot

You can also use [Valibot](https://valibot.dev/) 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.

```ts
import { useForm } from 'vee-validate';
import * as v from 'valibot';

const schema = 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`:

<Repl files={{ 'App.vue': 'CompositionValibotBasic' }} client:visible />

### 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](/guide/global-validators).

#### Validating with functions

Another option is to just use any 3rd party validation tool you prefer, something like [`validator.js`](https://github.com/validatorjs/validator.js/). Here is a quick example:

<Repl files={{ 'App.vue': 'CompositionValidateFnBasic' }} client:visible />

Or you could use any custom function.

<DocTip>

Schema libraries like zod and yup are great at defining schemas especially with nested values so it is recommended that you use any [standard schema](https://standardschema.dev/) library to get full benefits. As an added bonus, you get full typescript support with any compatible library.

</DocTip>

### 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`.

<Repl files={{ 'App.vue': 'CompositionDynamicSchemaComputed' }} client:visible />

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`:

<Repl files={{ 'App.vue': 'CompositionDynamicSchemaYupLazy' }} client:visible />
