Form
Building forms with React Hook Form and Zod validation.
Installation
bash
npx shadcn@latest add formnpm install react-hook-form zod @hookform/resolversImport
import { useForm } from "react-hook-form"import { z } from "zod"import { zodResolver } from "@hookform/resolvers/zod"import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage,} from "@/components/ui/form"import { Input } from "@/components/ui/input"import { Button } from "@/components/ui/button"Profile form
const formSchema = z.object({ username: z.string().min(2, { message: "Username must be at least 2 characters." }).max(50),})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) } 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="Enter username..." {...field} /> </FormControl> <FormDescription>This is your public display name.</FormDescription> <FormMessage /> </FormItem> )} /> <Button type="submit">Submit</Button> </form> </Form> )}Multiple field types
const formSchema = z.object({ email: z.string().email(), role: z.string({ required_error: "Please select a role." }), notifications: z.boolean().default(false),})// Fields: Input for email, Select for role, Checkbox for notifications// Each follows the same FormField > FormItem > FormControl patternProps
PROPTYPEDEFAULTDESCRIPTION
FormField——control (from useForm), name (string), render (({ field }) => ReactNode)FormItem——Wraps label + control + description + message (uses context)FormControl——Wraps the actual input componentFormLabel——Renders an accessible label linked to the controlFormDescription——Renders helper text below the controlFormMessage——Auto-displays the Zod validation error for this fieldKey pattern
FormFieldrender prop receivesfield— spread it onto your input:<Input {...field} />FormMessagereads errors automatically from form state — no extra wiring needed