Form
Building forms with VeeValidate and any Standard Schema library, such as Zod, Valibot, or ArkType.
Preview
Installation
npx sigma-ui@latest add formSchema validation
The form validates with any Standard Schema library: Zod 4, Valibot, ArkType, and others. The examples on this page use Zod. Install it next to vee-validate:
npm install zodtoTypedSchema ships with the form component, so no @vee-validate/zod adapter is needed. That adapter only supports Zod 3, and it breaks on Zod 4 schemas that use .default().
Usage
<Form /> and <FormField /> wrap vee-validate. A field is built from these parts:
<FormField v-slot="{ componentField }" name="username">
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input v-bind="componentField" />
</FormControl>
<FormDescription />
<FormMessage />
</FormItem>
</FormField>FormControl applies the right id, aria-describedby, and aria-invalid for the field state. Validation can use any Standard Schema library, such as Zod, Valibot, or ArkType.
Create a schema
toTypedSchema from the form component turns a Standard Schema into a vee-validate schema. It types the form values from the schema, including the input and output types, and fills initial values from defaults such as Zod's .default().
<script setup lang="ts">
import * as z from 'zod';
import { toTypedSchema } from '@/components/ui/form';
const formSchema = toTypedSchema(z.object({
username: z.string().min(2).max(50),
newsletter: z.boolean().default(false),
}));
</script>The same field with Valibot or ArkType:
import * as v from 'valibot';
import { type } from 'arktype';
import { toTypedSchema } from '@/components/ui/form';
const valibotSchema = toTypedSchema(v.object({
username: v.pipe(v.string(), v.minLength(2), v.maxLength(50)),
}));
const arktypeSchema = toTypedSchema(type({
username: '2 <= string <= 50',
}));Define the form
useForm is the recommended way to create the form, because the submitted values are typed from the schema. <Form /> is the alternative when you want the schema on the component.
<script setup lang="ts">
import { useForm } from 'vee-validate';
import * as z from 'zod';
import { toTypedSchema } 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">
<!-- fields -->
</form>
</template>The same schema passed to <Form>:
<template>
<Form :validation-schema="formSchema" @submit="onSubmit">
<!-- fields -->
</Form>
</template>Build the fields
Bind each input with the componentField slot prop. FormMessage renders the error for that field.
<script setup lang="ts">
import { useForm } from 'vee-validate';
import * as z from 'zod';
import { Button } from '@/components/ui/button';
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, toTypedSchema } 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="sigma-ui" v-bind="componentField" />
</FormControl>
<FormDescription>
This is your public display name.
</FormDescription>
<FormMessage />
</FormItem>
</FormField>
<Button type="submit">
Submit
</Button>
</form>
</template>A native <input> uses the field slot prop instead of componentField:
<FormField v-slot="{ field }" name="username">
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<input placeholder="sigma-ui" v-bind="field">
</FormControl>
<FormMessage />
</FormItem>
</FormField>Examples
The same FormField pattern works with the other fields:
Auto animate
FormKit AutoAnimate can animate fields as errors appear.
API Reference
FormItem
Slots
| Slot | Scope |
|---|---|
defaultThe content of the component. | - |
FormLabel
Props
| Prop | Type | Default |
|---|---|---|
forThe id of the element the label is associated with. | string | - |
asChildChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | boolean | - |
asThe element or component this component should render as. Can be overwritten by | AsTag | Component | "div" |
Slots
| Slot | Scope |
|---|---|
defaultThe content of the component. | - |
FormControl
Slots
| Slot | Scope |
|---|---|
defaultThe content of the component. | - |
FormMessage
This component has no props, events, or slots.
FormDescription
Slots
| Slot | Scope |
|---|---|
defaultThe content of the component. | - |
Accessibility
FormLabel and FormControl connect the label to the input. FormMessage is referenced with aria-describedby, and invalid fields get aria-invalid. Keep one label per field, and do not remove FormMessage if you still want the error announced.
| Key | Description |
|---|---|
| Tab | Moves focus to the next field or button. |
| Shift + Tab | Moves focus to the previous field or button. |
| Enter | Submits the form when focus is on a single-line field or the submit button. |