Basel Standard / Docs
Form
React Hook Form helper layer for accessible labels, descriptions, controls, and validation messages.
The Form helpers connect React Hook Form state to the visual primitives in this system. They keep ids, aria-describedby, invalid states, and message wiring consistent so each input does not need its own local accessibility bookkeeping.
Installation
Purchase access, then open /account/install to issue a registry token.Usage
import { useForm } from "react-hook-form"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
const form = useForm({
defaultValues: { title: "" },
})
<Form {...form}>
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Release title</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>Name the review before sending it out.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</Form>
Why This Primitive Exists
This layer does not replace React Hook Form. It narrows the repetitive glue around it.
- Use it when a control needs consistent label, description, and error wiring.
- Use it when different inputs should share the same accessibility contract without per-field ids.
- Avoid it for static layout-only forms where
Fieldis enough and no managed validation state is involved.
Examples
Basic Managed Field
The smallest useful pattern is one FormField mapped to one FormItem, with a label, control, helper text, and message output.
<FormField
control={form.control}
name="title"
rules={{ required: "Enter the release title." }}
render={({ field }) => (
<FormItem>
<FormLabel>Release title</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>Use the internal release name.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
Composed Controls
FormControl works with wrapped inputs such as Select and checkbox layouts because it provides the shared ids and described-by chain through a slot.
<FormControl>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<SelectTrigger>
<SelectValue placeholder="Choose a cadence" />
</SelectTrigger>
</Select>
</FormControl>
In Context
The helpers stay useful when the form sits inside a larger card or review surface. Labels and messages remain attached even when the layout around them becomes more editorial.
Review workflow
<CardContent>
<FormField
control={form.control}
name="note"
render={({ field }) => (
<FormItem>
<FormLabel>Release note</FormLabel>
<FormControl>
<Textarea rows={4} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</CardContent>
Guidance
Keep Each FormField Narrow
- Map one form value to one
FormField. - Put complex layout around the field, not inside the render callback when possible.
- If several controls belong to one visual group, use surrounding layout primitives and keep each control's accessibility chain intact.
Let FormControl Own The Wiring
- Wrap the actual interactive element with
FormControl. - Do not hand-roll
id,aria-describedby, or invalid attributes unless a specific control requires it. - Keep
FormLabel,FormDescription, andFormMessageinside the sameFormItemso they resolve against the current field context.
Choose Field Vs Form Intentionally
- Use
Fieldwhen you need layout structure without form-library state. - Use
Formwhen the control participates in validation, submission, or controlled state. - It is reasonable to combine both patterns, but avoid duplicating label or message responsibilities.
API Reference
Exports
| Export | Renders | Notes |
|---|---|---|
Form | FormProvider | React Hook Form provider alias. |
FormField | Controller | Bridges a named field into the context used by the helper components. |
FormItem | div | Generates a stable id base for one field. |
FormLabel | Label | Resolves htmlFor from the active field context. |
FormControl | Slot | Applies id, aria-describedby, and aria-invalid to the child control. |
FormDescription | p | Helper text tied to the current form field. |
FormMessage | p | Error or custom message tied to the current form field. |
useFormField | hook | Exposes the resolved ids, field name, and React Hook Form field state. |
FormField
FormField accepts the standard ControllerProps<TFieldValues, TName> API from React Hook Form, including:
| Prop | Type | Notes |
|---|---|---|
control | RHF control object | Required. |
name | FieldPath<TFieldValues> | Required field name. |
rules | controller rules | Optional validation rules. |
render | render callback | Receives { field, fieldState, formState }. |
FormControl
FormControl forwards the slot props and automatically derives:
| Output | Behavior |
|---|---|
id | Set to the generated form-item id. |
aria-describedby | Includes the description id and the message id when an error exists. |
aria-invalid | Mirrors the active field error state. |
Use these helpers inside FormField and FormItem. Outside that context, useFormField and the field-bound components will throw.