Skip to content
UI

Search docs

Search components and documentation

New
Menu

Form

Building forms with react-hook-form and zod.

This is your public display name.

Installation

npx logic2b@latest add form

Anatomy

import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import { z } from "zod"

import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form"

const formSchema = z.object({
  username: z.string().min(2),
})

function ProfileForm() {
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: { username: "" },
  })

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(console.log)}>
        <FormField
          control={form.control}
          name="username"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Username</FormLabel>
              <FormControl>
                <input {...field} />
              </FormControl>
              <FormDescription>Your public display name.</FormDescription>
              <FormMessage />
            </FormItem>
          )}
        />
      </form>
    </Form>
  )
}

FormField wraps react-hook-form’s Controller and provides field context to FormLabel, FormControl, FormDescription and FormMessage — they read validation state (error, id) from that context, so labels and messages stay wired to the right input automatically.

API

Form is an alias for react-hook-form’s FormProvider, so spread your useForm return value onto it. The pieces below consume field context.

Prop Type Default
control (FormField) Control — from useForm()
name (FormField) string — the field path
render (FormField) ({ field, fieldState, formState }) => ReactElement
...form (Form) the object returned by useForm()
useFormField() hook returning { id, name, error, formItemId, formDescriptionId, formMessageId }

Examples

Sign In

Validate an email and password with zod.

Textarea

This appears on your public profile.

Accessibility

  • FormControl sets id, aria-describedby and aria-invalid from field context, so the label, description and error message are all wired to the input without manual htmlFor / id bookkeeping.
  • On validation failure FormMessage renders the error text with the id referenced by aria-describedby, and FormLabel turns destructive — the invalid state is conveyed both visually and to screen readers.
  • Because each control gets a unique generated id, labels always point at the right input even when the field is reused in a list.