Skip to content

Form

React Hook Form scaffolding that wires labels, controls, descriptions, and validation messages.

Stablev0.4.2added in v0.1.0@garn/ui/form
On this page

Default

A single RHF-bound, labeled field with description + submit.

Installation

garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.

terminal
npx garn-ui add form
First time? Set up garn in your project
Registry
form
Deps
react-hook-form@radix-ui/react-slot
Registry deps
utilslabel

Import

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

Anatomy

The parts this component is built from — compose them to assemble it.

FormRequired

= RHF FormProvider. Spread your useForm() return into it.

FormFieldRequired

Binds a field by name to a control via RHF's Controller (render-prop).

FormItemRequired[data-slot="form-item"]

One field's row; provides the id that ties label/control/message together.

FormLabel[data-slot="form-label"]

Label wired to the control (htmlFor), turns destructive on error.

FormControlRequired[data-slot="form-control"]

Slot that injects id + aria-describedby + aria-invalid onto your control.

FormDescription[data-slot="form-description"]

Helper text, linked via aria-describedby.

FormMessage[data-slot="form-message"]

Validation error, linked via aria-describedby; renders nothing when valid.

Props

The component's public props and their types.

No component-specific props — this is a thin wrapper over its native element.

Styling

Target these data-slots and states, and remap these tokens, to restyle without forking the component.

[data-slot="form-item"]

States

SelectorState
[aria-invalid=true]invalid
PropertyTokenTier
fieldGap--garn-gap-fieldsemantic · air

When to use

Build accessible forms with React Hook Form — consistent label / control / description / error wiring out of the box.

Reach for something else when

  • A single, simple field with no validation (just use the control + a Label)
  • a form not using react-hook-form.

Overview

Form is the accessible wiring layer over React Hook Form: it ties each field's label, control, description, and error message together so the ARIA plumbing is correct by default. Form is the RHF provider, FormField binds a name via Controller, and within a FormItem the FormLabel / FormControl / FormDescription / FormMessage parts share one id set — FormControl injects id + aria-describedby + aria-invalid onto your control. Use it for any real form; a single uncontrolled field doesn't need the machinery.

Guidelines

Wrap each field in FormField + FormItem. That's what generates the shared id and links label↔control↔description↔error; reaching past it to hand-wire htmlFor/aria-* reintroduces the bugs Form exists to prevent. Put your control inside FormControl so the a11y attributes land on it.

Validate with a schema and surface errors inline. Drive RHF with a resolver (Zod/Yup) so FormMessage renders the real validation message beside the field and aria-invalid styles it; show errors on submit/blur, not on every keystroke. Keep one clear message per field.

Write helpful labels and descriptions. Use FormDescription for format hints and requirements rather than cramming them into the label or a placeholder, mark optional fields explicitly, and keep the submit action's state honest (disable/spinner while pending).

Best practices

Do
  • Wrap fields in FormField (control + name) and render the control inside FormControl.
  • Put one FormItem per field so the label/control/message ids stay linked.
  • Let FormMessage render the error — it pulls from RHF field state automatically.
Don't
  • Don't hand-wire aria-describedby/aria-invalid — FormControl does it.
  • Don't call useFormField outside a FormField — it throws.

Content guidelines

  • Write field labels as short noun phrases; reserve helper text for one line of guidance.
  • Make error messages specific and fixable (“Enter a valid email”), tied to the field.

Troubleshooting

useFormField should be used within <FormField> thrown at runtime.

Cause. A Form* part rendered outside a FormField/FormItem.

Fix. Wrap each field's parts in FormField → FormItem.

The field doesn't track input or validate.

Cause. The control isn't wired to RHF.

Fix. Render the control inside FormControl and spread the Controller field onto it.

Accessibility

Role
form
A1.3.1Info and RelationshipsA3.3.1Error IdentificationA3.3.2Labels or InstructionsA4.1.2Name, Role, Value
  • FormControl auto-wires id + aria-describedby (description + message) + aria-invalid onto the control.
  • FormLabel's htmlFor targets the control; FormMessage announces the validation error.