Components
Field
A composable form-field primitive that pairs any control with a label, helper text, and validation messaging — with vertical, horizontal, and responsive layouts.
Basic usage
Wrap a control in Field and pair a FieldLabel with the input via matching htmlFor / id.
We'll never share your email with anyone.
Field groups
Use FieldGroup to stack several fields with consistent spacing. It also opens the container query that orientation="responsive" measures against, so responsive fields react to the width of the form rather than the width of the window.
Optional — appears on your invoices.
Validation
Add data-invalid to the field and render a FieldError to surface a message with role="alert". Instead of children you can hand it an errors array in react-hook-form's shape — one message prints inline, several become a bulleted list, and an array with nothing to say renders no element at all rather than an empty red gap.
- Use lowercase letters only.
- Spaces are not allowed.
Fieldsets & legends
Group related fields under a semantic FieldSet with a FieldLegend. Set orientation="horizontal" on a field to align its label and control in a row.
Separators with a label
Give FieldSeparator children and it splits the rule in two, placing the text between the halves. The familiar trick — one full-width rule with an opaque bg-background chip painted over the middle — only lines up when the form sits directly on the page. Inside a Card, a Popover, or a tinted Alert the chip shows up as a rectangle in the wrong colour, and in light mode the two tokens happen to match so nobody notices until the theme flips. Cutting the line needs to know nothing about what is behind it.
Choice cards
A FieldLabel that wraps a whole Field turns into a selectable card: it goes full width, grows a border, pads its contents, and reacts to the control inside reporting data-state="checked". Pair FieldContent with FieldTitle for the text column so the control can sit beside a title and a description.
Props
Field props:
| Prop | Type | Default | Description |
|---|---|---|---|
orientation | 'vertical' | 'horizontal' | 'responsive' | 'vertical' | Stacks the label and control (vertical), aligns them in a row (horizontal), or switches from stacked to row at the @md container breakpoint of the enclosing FieldGroup (responsive). |
data-invalid | 'true' | — | Turns the field’s inherited text colour destructive, which reaches the label and any child that does not set its own colour. FieldDescription keeps its muted colour; FieldError is always destructive. |
className | string | — | Additional Tailwind classes merged onto the group element. |
...rest | HTMLAttributes<HTMLDivElement> | — | Native div props are forwarded. Field always renders role="group" and data-orientation. |
The other parts. Each one forwards its native props and merges className:
| Prop | Type | Default | Description |
|---|---|---|---|
FieldSet | fieldset props | — | Native <fieldset>. Tightens its own gap when it directly contains a checkbox or radio group. |
FieldLegend variant | 'legend' | 'label' | 'legend' | Native <legend> at base size for a section title, or sm size when the group is nested and should read as a plain label. |
FieldGroup | div props | — | Stacks fields with a consistent gap and opens the @container/field-group used by orientation="responsive". Nested groups tighten automatically. |
FieldLabel | Radix Label props | — | Native <label>. Wrap a whole Field in it to get a choice card — border, padding, and a checked state driven by the control inside. |
FieldTitle | div props | — | The bold first line inside a choice card. Use it instead of FieldLabel when a real <label> already wraps the card. |
FieldContent | div props | — | Column wrapper for the text half of a horizontal field, so the control can sit beside a title plus description. |
FieldDescription | p props | — | Muted helper text. Links inside it are underlined and pick up the link hover token. |
FieldSeparator children | ReactNode | — | With content, the rule is split and the text sits between the two halves; without it, a single full-width rule. |
FieldError errors | Array<{ message?: string } | undefined> | — | Ignored when children are given. One message renders inline, several render as a bulleted list, entries without a message are dropped, and an empty result renders nothing at all. |
fieldVariants | cva helper | — | The exported variants function behind Field — use it to give another element the same orientation layout. |
Accessibility
FieldLabelrenders a native<label>— match itshtmlForto the control'sidso clicking the label focuses the input and screen readers announce the name.Fieldappliesrole="group", semantically associating the label, control, and helper text as a single unit.FieldErrorcarriesrole="alert"so validation messages are announced when they appear; setaria-invalidon the control to mark it invalid.FieldSetrenders a native<fieldset>andFieldLegenda<legend>, giving grouped controls (radios, checkboxes) an accessible group label.- Error text uses
--destructive-ink, the red step meant to stand on the page background, not--destructive, which is the fill behind a delete button and drops under 4.5:1 as text on a card in dark mode. - On a selected choice card the tint is only about 1.03:1 against the page, so the border carries the state. It uses
--primary-inkrather than raw gold, which would land at 1.86:1 and fail the 3:1 threshold of WCAG 1.4.11 — and the radio or checkbox inside remains the real, announced state.