VeeValidate

Building forms with VeeValidate and Zod.

Forms are tricky. They are one of the most common things you'll build in a web application, but also one of the most complex.

Well-designed HTML forms are:

  • Well-structured and semantically correct.
  • Easy to use and navigate (keyboard).
  • Accessible with ARIA attributes and proper labels.
  • Has support for client and server side validation.
  • Well-styled and consistent with the rest of the application.

In this guide, we will take a look at building forms with vee-validate and zod. We're going to use a <FormField> component to compose accessible forms using Radix Vue components.

Features

The <Form /> component is a wrapper around the vee-validate library. It provides a few things:

  • Composable components for building forms.
  • A <FormField /> component for building controlled form fields.
  • Form validation using zod.
  • Applies the correct aria attributes to form fields based on states, handle unique IDs
  • Built to work with all Radix Vue components.
  • Bring your own schema library. We use zod but you can use any other supported schema validation you want, like yup or valibot.
  • You have full control over the markup and styling.

vee-validate makes use of two flavors to add validation to your forms.

  • Composition API
  • Higher-order components (HOC)

Anatomy

vue
<Form>
  <FormField v-slot="{ ... }">
    <FormItem>
      <FormLabel />
      <FormControl>
        <!-- any Form Input component or native input elements -->
      </FormControl>
      <FormDescription />
      <FormMessage />
    </FormItem>
  </FormField>
</Form>

Example

Input Component

vue
<FormField v-slot="{ componentField }">
  <FormItem>
    <FormLabel>Username</FormLabel>
    <FormControl>
      <Input placeholder="shadcn" v-bind="componentField" />
    </FormControl>
    <FormDescription />
    <FormMessage />
  </FormItem>
</FormField>

Installation

bash
npx shadcn-vue@latest add form

Usage

Create a form schema

Define the shape of your form using a Zod schema. You can read more about using Zod in the Zod documentation.

Use @vee-validate/zod to integrate Zod schema validation with vee-validate

toTypedSchema also makes the form values and submitted values typed automatically and caters for both input and output types of that schema.

vue
<script setup lang="ts">
import { toTypedSchema } from '@vee-validate/zod'
import * as z from 'zod'

const formSchema = toTypedSchema(z.object({
  username: z.string().min(2).max(50),
}))
</script>

Define a form

Use the useForm composable from vee-validate or use <Form /> component to create a form.

vue
<script setup lang="ts">
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import * as z from 'zod'

import {
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage
} from '@/components/ui/form'

const formSchema = toTypedSchema(z.object({
  username: z.string().min(2).max(50),
}))

const form = useForm({
  validationSchema: formSchema,
})

const onSubmit = form.handleSubmit((values) => {
  console.log('Form submitted!', values)
})
</script>

<template>
  <form @submit="onSubmit">
    ...
  </form>
</template>

Build your form

Based on last step we can either use <Form /> component or useForm composable useForm is recommended because values are typed automatically

vue
<script setup lang="ts">
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import * as z from 'zod'

import { Button } from '@/components/ui/button'
import {
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'

const formSchema = toTypedSchema(z.object({
  username: z.string().min(2).max(50),
}))

const form = useForm({
  validationSchema: formSchema,
})

const onSubmit = form.handleSubmit((values) => {
  console.log('Form submitted!', values)
})
</script>

<template>
  <form @submit="onSubmit">
    <FormField v-slot="{ componentField }" name="username">
      <FormItem>
        <FormLabel>Username</FormLabel>
        <FormControl>
          <Input type="text" placeholder="shadcn" v-bind="componentField" />
        </FormControl>
        <FormDescription>
          This is your public display name.
        </FormDescription>
        <FormMessage />
      </FormItem>
    </FormField>
    <Button type="submit">
      Submit
    </Button>
  </form>
</template>

Done

That's it. You now have a fully accessible form that is type-safe with client-side validation.

This is your public display name.

Examples

See the following links for more examples on how to use the vee-validate features with other components:

Extras

This example shows how to add motion to your forms with Formkit AutoAnimate

This is your public display name.

Edit this page on GitHub