Skip to content

Field

Pairs a control with a label and optional description, as a row or a card.

Stablev0.4.2added in v0.2.0@garn/ui/field
On this page

Default

Settings row: label + description + switch.

Card

Card appearance.

Controls

Same Field with a checkbox and radio items.

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 field
First time? Set up garn in your project
Registry
field
Deps
class-variance-authority
Registry deps
utilslabel

Import

import { Field } from "@garn/ui/field";

Props

The component's public props and their types.

appearance
Description
No description.
Type
"card" | "plain"
Default
plain
orientation
Description
No description.
Type
"end" | "start"
Default
start
childrenRequired
Description
The control: a Switch, Checkbox, or single RadioGroupItem.
Type
React.ReactElement<unknown, string | React.JSXElementConstructor<any>>
description
Description
Optional one-line explanation, wired to the control via `aria-describedby`.
Type
React.ReactNode
labelRequired
Description
The setting name — becomes the control's accessible name via `htmlFor`.
Type
React.ReactNode
size
Description
Label text size — pair with the control's own `size`.
Type
"lg" | "md" | "sm"

Plus 279 inherited native <div> attributes.

Styling

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

[data-slot="field"]

States

SelectorState
[data-appearance=card]:has([data-state=checked])checked
appearance
orientation
PropertyTokenTier
cardSurface--garn-cardsemantic
cardPadding--garn-pad-panelsemantic · air
selectedEdge--garn-brand-solidbrandablesemantic
descriptionColor--garn-muted-foregroundsemantic

When to use

Pair a Switch / Checkbox / RadioGroupItem with a label (+ optional description), as a plain row or a selectable card.

Reach for something else when

  • A form bound to react-hook-form with validation messages (use Form)
  • a label with no control (use Label directly).

Overview

Field is the shared control-field: it takes a Switch, Checkbox, or a single RadioGroupItem as its child and lays it beside a label and an optional one-line description, doing the accessible-name (htmlFor) and description (aria-describedby) wiring for you. appearance="card" turns the row into a bordered option card that highlights when its control is checked, and orientation flips between a control-leading row (checkboxes, radios) and a text-leading settings row (the switch on the right). Because it wires by cloning the control, one Field looks and behaves identically whichever control drives it. For a form bound to react-hook-form with validation messages, use Form instead; Field is the lightweight, standalone layout.

Guidelines

Give every Field a label — that IS the control's name. The label is required and is associated by htmlFor, so you don't add a separate aria-label to the control. Add a description only when the effect isn't obvious from the label.

Use card for selectable options, plain for dense settings. A list of option cards (pick-one radios, feature toggles) reads well as card; a long settings screen is calmer as plain rows with a divider. The card's checked-edge highlight only makes sense when one option among several is chosen.

Match orientation to the control. Checkboxes and radios read naturally control-first (start); a switch usually sits at the end of a settings row (end), label on the left. Keep the whole group consistent.

One control per Field. Field clones a single child to wire it. For a group of radios, wrap each RadioGroupItem in its own Field inside one RadioGroup.

Best practices

Do
  • Pass exactly one control as children; let Field wire the label + description.
  • Use appearance="card" for pick-one option cards, orientation="end" for switch settings rows.
Don't
  • Don't add an aria-label to the control — the Field's label already names it.
  • Don't put multiple controls in one Field; wrap each in its own Field.

Content guidelines

  • Label states what the setting is, in sentence case; the description is one short line, not a paragraph.
  • Don't repeat the label in the description.

Troubleshooting

The control has no accessible name / axe flags it.

Cause. The Field's label is missing, or an aria-label on the control competes with it.

Fix. Always pass label; don't add aria-label to the control — Field names it via htmlFor.

The card doesn't highlight when selected.

Cause. The child control doesn't expose data-state=checked (or isn't checked).

Fix. Use a Switch / Checkbox / RadioGroupItem as the child — the card reads their data-state via :has().

Accessibility

Accessibility requirements

warn

Pass exactly one control (Switch, Checkbox, or a RadioGroupItem) as children — Field clones it to inject the id + aria-describedby.

when children is not a single control element

A1.3.1Info and RelationshipsA4.1.2Name, Role, Value
  • The Label's htmlFor points at the control's generated id, so the control's accessible name is the label text.
  • A description is given an id and wired onto the control via aria-describedby (preserving any aria-describedby the control already had).
  • Clicking the label toggles the control (native label association); the description is not part of the accessible name.