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.

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.

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.

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.

Billing address

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.

or

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:

PropTypeDefaultDescription
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.
classNamestringAdditional Tailwind classes merged onto the group element.
...restHTMLAttributes<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:

PropTypeDefaultDescription
FieldSetfieldset propsNative <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.
FieldGroupdiv propsStacks fields with a consistent gap and opens the @container/field-group used by orientation="responsive". Nested groups tighten automatically.
FieldLabelRadix Label propsNative <label>. Wrap a whole Field in it to get a choice card — border, padding, and a checked state driven by the control inside.
FieldTitlediv propsThe bold first line inside a choice card. Use it instead of FieldLabel when a real <label> already wraps the card.
FieldContentdiv propsColumn wrapper for the text half of a horizontal field, so the control can sit beside a title plus description.
FieldDescriptionp propsMuted helper text. Links inside it are underlined and pick up the link hover token.
FieldSeparator childrenReactNodeWith content, the rule is split and the text sits between the two halves; without it, a single full-width rule.
FieldError errorsArray<{ 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.
fieldVariantscva helperThe exported variants function behind Field — use it to give another element the same orientation layout.

Accessibility

  • FieldLabel renders a native <label> — match its htmlFor to the control's id so clicking the label focuses the input and screen readers announce the name.
  • Field applies role="group", semantically associating the label, control, and helper text as a single unit.
  • FieldError carries role="alert" so validation messages are announced when they appear; set aria-invalid on the control to mark it invalid.
  • FieldSet renders a native <fieldset> and FieldLegend a <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-ink rather 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.