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.

Required, min 2 characters.

Must be a valid email format.

Min 10 and max 160 characters.

Select one from the options.

Native browser select component.

Receive emails about your account activity.

You must accept this to proceed.

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.

On this page