Documentation Index
Fetch the complete documentation index at: https://mintlify.com/shadcn-ui/ui/llms.txt
Use this file to discover all available pages before exploring further.
Installation
The Form component is built on top of React Hook Form and Zod for schema validation.
npm install react-hook-form @hookform/resolvers zod
Or use the CLI:
npx shadcn@latest add form
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
Create a form schema
Define your form schema using Zod:
import { z } from "zod"
const formSchema = z.object({
username: z.string().min(2, {
message: "Username must be at least 2 characters.",
}),
})
Define a form
Use the useForm hook from React Hook Form to create a form:
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"
const formSchema = z.object({
username: z.string().min(2, {
message: "Username must be at least 2 characters.",
}),
})
export function ProfileForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
username: "",
},
})
function onSubmit(values: z.infer<typeof formSchema>) {
console.log(values)
}
}
Build your form
Compose form fields using the Form components:
import { Button } from "@/components/ui/button"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
export function ProfileForm() {
// ...
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
<FormDescription>
This is your public display name.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
)
}
Component API
The root form provider built on React Hook Form’s FormProvider.
Props:
- All props from React Hook Form’s
FormProvider
FormField
A controlled form field.
Props:
control - Form control from useForm
name - Field name
render - Render function that receives field props
FormItem
Container for a form field.
FormLabel
Label for a form field.
Props:
- Automatically shows error state
FormControl
Wrapper for the form control element.
Props:
- Automatically handles
id, aria-describedby, and aria-invalid
FormDescription
Description text for a form field.
FormMessage
Displays form field errors.
Props:
- Automatically displays the error message for the field
useFormField
A custom hook to access form field context.
Returns:
id - Field ID
name - Field name
formItemId - Form item ID
formDescriptionId - Description ID
formMessageId - Message ID
error - Field error
invalid - Whether field is invalid
isDirty - Whether field is dirty
isTouched - Whether field is touched
Examples
A simple form with an input field:
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"
import { Button } from "@/components/ui/button"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
const formSchema = z.object({
username: z.string().min(2).max(50),
})
export function InputForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
username: "",
},
})
function onSubmit(values: z.infer<typeof formSchema>) {
console.log(values)
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
<FormDescription>
This is your public display name.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
)
}
Textarea
A form with a textarea:
import { Textarea } from "@/components/ui/textarea"
const formSchema = z.object({
bio: z
.string()
.min(10, {
message: "Bio must be at least 10 characters.",
})
.max(160, {
message: "Bio must not be longer than 160 characters.",
}),
})
// In your form:
<FormField
control={form.control}
name="bio"
render={({ field }) => (
<FormItem>
<FormLabel>Bio</FormLabel>
<FormControl>
<Textarea
placeholder="Tell us a little bit about yourself"
className="resize-none"
{...field}
/>
</FormControl>
<FormDescription>
You can <span>@mention</span> other users and organizations.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
Checkbox
A form with a checkbox:
import { Checkbox } from "@/components/ui/checkbox"
const formSchema = z.object({
mobile: z.boolean().default(false).optional(),
})
// In your form:
<FormField
control={form.control}
name="mobile"
render={({ field }) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<div className="space-y-1 leading-none">
<FormLabel>
Use different settings for my mobile devices
</FormLabel>
<FormDescription>
You can manage your mobile notifications in the mobile settings page.
</FormDescription>
</div>
</FormItem>
)}
/>
A form with a select:
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const formSchema = z.object({
email: z
.string({
required_error: "Please select an email to display.",
})
.email(),
})
// In your form:
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a verified email to display" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="m@example.com">m@example.com</SelectItem>
<SelectItem value="m@google.com">m@google.com</SelectItem>
<SelectItem value="m@support.com">m@support.com</SelectItem>
</SelectContent>
</Select>
<FormDescription>
You can manage email addresses in your email settings.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
Radio Group
A form with a radio group:
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
const formSchema = z.object({
type: z.enum(["all", "mentions", "none"], {
required_error: "You need to select a notification type.",
}),
})
// In your form:
<FormField
control={form.control}
name="type"
render={({ field }) => (
<FormItem className="space-y-3">
<FormLabel>Notify me about...</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
defaultValue={field.value}
className="flex flex-col space-y-1"
>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="all" />
</FormControl>
<FormLabel className="font-normal">
All new messages
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="mentions" />
</FormControl>
<FormLabel className="font-normal">
Direct messages and mentions
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="none" />
</FormControl>
<FormLabel className="font-normal">Nothing</FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
A form with a switch:
import { Switch } from "@/components/ui/switch"
const formSchema = z.object({
marketing_emails: z.boolean().default(false).optional(),
})
// In your form:
<FormField
control={form.control}
name="marketing_emails"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
<div className="space-y-0.5">
<FormLabel className="text-base">
Marketing emails
</FormLabel>
<FormDescription>
Receive emails about new products, features, and more.
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
Accessibility
- Built on React Hook Form for performance
- Automatic error handling and display
- Proper ARIA attributes
- Label associations
- Error announcements for screen readers
API Reference
See the React Hook Form and Zod documentation for complete API details.