Forms/React Hook Form

React Hook Form

Form integration with React Hook Form library.

Themed

When to Use

Use React Hook Form when you need to:

  • Build complex forms with schema-based validation using Zod
  • Manage forms with many interdependent fields that need performant re-rendering
  • Integrate validation across multiple input types (inputs, selects, checkboxes, dates, comboboxes)
  • Provide real-time or on-submit validation with accessible error messages
  • Handle form state (dirty, touched, errors) with minimal boilerplate

When Not to Use

  • Simple single-field forms without validation - use the Field component directly
  • Forms where you prefer a headless, framework-agnostic approach - consider TanStack Form
  • Static display forms or read-only views - use Label with plain inputs
  • Server-only form actions (Next.js Server Actions) - use the native Form component

Simple Form

Basic form with a single input field and Zod validation. The minimal setup for React Hook Form integration.

This is your public display name.

Contact Form

Multi-field form with Input and Textarea components, each validated through a shared Zod schema.

We will never share your email.

Enter your message above.

Multiple Input Types

Demonstrates integration with Input, Select, Checkbox, and Switch components in a single form.

Your unique username.

Your account role.

Receive notifications about account activity.

Receive emails about new features.

Radio Group

Form with RadioGroup for single-choice selection, validated via a Zod enum.

Date Picker

Calendar-based date picker integrated with React Hook Form using a Popover trigger.

Select the event date.

Combobox

Searchable combobox selector using Command and Popover components with form validation.

Select your preferred language.

UX & Design Guidelines

Visual Hierarchy

Group related fields together using consistent spacing. Place the most important fields first and use FormDescription to provide context beneath each field. Use FormMessage for validation errors directly below their fields so users can quickly scan and fix issues.

Spacing & Layout

Use space-y-4 between form fields for comfortable vertical rhythm. For inline fields (e.g., Checkbox with label), use flex flex-row items-start space-x-3. Place the submit button at the bottom with pt-2 or more to visually separate it from form fields.

Responsive Behavior

Stack all form fields vertically on mobile for single-column readability. On larger screens, you may arrange short fields (e.g., first name / last name) side by side using grid grid-cols-2 gap-4. Ensure touch targets for checkboxes, switches, and radio buttons meet the minimum 44x44px guideline.

Color & Contrast

Error messages use text-destructive for clear visibility against both light and dark backgrounds. Description text uses text-muted-foreground to differentiate helper text from labels. Invalid fields display a red border ring to provide an additional visual cue beyond the error message alone.

Accessibility

Keyboard Navigation

  • Tab — Move focus between form fields in DOM order
  • Enter — Submit the form when a submit button is focused
  • Space — Toggle checkboxes, switches, and radio items
  • Arrow keys — Navigate between radio group options
  • Escape — Close popover-based fields (date picker, combobox)

Screen Reader Support

  • FormLabel automatically associates with FormControl via generated htmlFor / id pairs
  • FormDescription is linked via aria-describedby so assistive tech reads helper text alongside the field
  • Error messages use role="alert" to immediately announce validation errors
  • Invalid fields receive aria-invalid="true" automatically when validation fails

Focus Management

When shouldFocusError is enabled (default), the first field with a validation error is automatically focused after submission. This helps keyboard and screen reader users immediately locate and fix the issue. Visible focus rings use focus-visible:ring-[3px] for all form controls.

Error Announcements

Validation errors rendered by FormMessage are wrapped in an element with role="alert", ensuring they are announced by screen readers immediately when they appear. Each error is also linked to its field via aria-describedby for contextual association.

ARIA Attributes

{/* FormLabel auto-associates with FormControl via htmlFor */}
<FormField
  control={form.control}
  name="email"
  render={({ field }) => (
    <FormItem>
      <FormLabel>Email</FormLabel>
      <FormControl>
        {/* Renders: <input id="form-item-email" aria-describedby="form-item-email-description" /> */}
        <Input {...field} />
      </FormControl>
      {/* Renders: <p id="form-item-email-description"> */}
      <FormDescription>We will never share your email.</FormDescription>
      {/* When invalid, renders: <p id="form-item-email-error" role="alert"> */}
      <FormMessage />
    </FormItem>
  )}
/>

{/* Invalid fields automatically receive aria-invalid="true" */}
{/* Error messages are announced to screen readers via role="alert" */}

API Reference

Key options for the useForm hook from React Hook Form.

PropTypeDefaultDescription
resolverrequiredResolverIntegrates external validation (e.g., zodResolver). Enables schema-based validation with Zod, Yup, or Joi.
defaultValuesrequiredDefaultValues<TFieldValues>Default values for form fields. Required for controlled components like Select, Checkbox, and Switch.
mode"onSubmit" | "onBlur" | "onChange" | "onTouched" | "all""onSubmit"Determines when validation is triggered. Use 'onChange' for real-time feedback or 'onBlur' for validate-on-leave.
reValidateMode"onSubmit" | "onBlur" | "onChange""onChange"When to re-validate after an error has been shown. Defaults to re-validating on every change.
criteriaMode"firstError" | "all""firstError"Whether to show only the first error or all errors for a field.
shouldFocusErrorbooleantrueWhen true, automatically focuses the first field with an error after submission.

FormField Props

Props for the FormField component that connects each input to the form.

PropTypeDefaultDescription
controlrequiredControl<TFieldValues>The control object from useForm. Connects the field to the form state.
namerequiredPath<TFieldValues>The name of the field. Must match a key in your Zod schema / defaultValues.
renderrequired({ field, fieldState, formState }) => ReactElementRender prop that provides field binding (value, onChange, onBlur, ref) and state (error, isDirty, isTouched).
rulesRegisterOptionsInline validation rules. Typically unused when using a resolver (Zod) for schema-level validation.
defaultValueTFieldValues[name]Default value for this specific field. Overrides the form-level defaultValues.