Components
Base Components
Form
Building forms with React Hook Form and Zod.
Example
Comprehensive Form
This demo showcases Zod validation integrated with every input type in the library.
Installation
Create a form.tsx file and paste the following code into it.
"use client"import * as React from "react"import * as LabelPrimitive from "radix-ui"import { Slot } from "@radix-ui/react-slot"import { Controller, ControllerProps, FieldPath, FieldValues, FormProvider, useFormContext,} from "react-hook-form"import { cn } from "@/lib/utils"const Form = FormProvidertype FormFieldContextValue< TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,> = { name: TName}const FormFieldContext = React.createContext<FormFieldContextValue>( {} as FormFieldContextValue)const FormField = < TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,>({ ...props}: ControllerProps<TFieldValues, TName>) => { return ( <FormFieldContext.Provider value={{ name: props.name }}> <Controller {...props} /> </FormFieldContext.Provider> )}const useFormField = () => { const fieldContext = React.useContext(FormFieldContext) const itemContext = React.useContext(FormItemContext) const { getFieldState, formState } = useFormContext() const fieldState = getFieldState(fieldContext.name, formState) if (!fieldContext) { throw new Error("useFormField should be used within <FormField>") } const { id } = itemContext return { id, name: fieldContext.name, formItemId: `${id}-form-item`, formDescriptionId: `${id}-form-item-description`, formMessageId: `${id}-form-item-message`, ...fieldState, }}type FormItemContextValue = { id: string}const FormItemContext = React.createContext<FormItemContextValue>( {} as FormItemContextValue)const FormItem = React.forwardRef< HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => { const id = React.useId() return ( <FormItemContext.Provider value={{ id }}> <div ref={ref} className={cn("grid gap-1.5", className)} {...props} /> </FormItemContext.Provider> )})FormItem.displayName = "FormItem"const FormLabel = React.forwardRef< HTMLLabelElement, React.LabelHTMLAttributes<HTMLLabelElement>>(({ className, ...props }, ref) => { const { error, formItemId } = useFormField() return ( <label ref={ref} className={cn( "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", error && "text-destructive", className )} htmlFor={formItemId} {...props} /> )})FormLabel.displayName = "FormLabel"const FormControl = React.forwardRef< React.ElementRef<typeof Slot>, React.ComponentPropsWithoutRef<typeof Slot>>(({ ...props }, ref) => { const { error, formItemId, formDescriptionId, formMessageId } = useFormField() return ( <Slot ref={ref} id={formItemId} aria-describedby={ !error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}` } aria-invalid={!!error} {...props} /> )})FormControl.displayName = "FormControl"const FormDescription = React.forwardRef< HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(({ className, ...props }, ref) => { const { formDescriptionId } = useFormField() return ( <p ref={ref} id={formDescriptionId} className={cn("text-muted-foreground !leading-none text-[0.8rem] mt-0 mb-1", className)} {...props} /> )})FormDescription.displayName = "FormDescription"const FormMessage = React.forwardRef< HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(({ className, children, ...props }, ref) => { const { error, formMessageId } = useFormField() const body = error ? String(error?.message) : children if (!body) { return null } return ( <p ref={ref} id={formMessageId} className={cn("text-destructive text-sm font-medium !my-0", className)} {...props} > {body} </p> )})FormMessage.displayName = "FormMessage"export { useFormField, Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage, FormField,}Check the import paths to ensure they match your project setup.
Usage
import { Form } from "@/components/ui/form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import * as z from "zod";Features
- Accessible by default.
- Works with React Hook Form.
- Validation with Zod.
- Support for Tooltips and complex layouts.