Checkbox · RadioGroup · Switch Atom

Three controls with one shape: the control, its label, and an optional description line — wired together, because a checkbox whose label is not clickable is a bug that ships constantly.

Usage

Billed on the 1st, cancel anytime.

Two months free, billed today.

Available on Business plans.

One message per payment, nothing else.

Turns off transforms across the interface.

Sent every Monday morning.

tsx
<RadioGroup  value={plan}  onValueChange={setPlan}  options={[    { value: 'monthly', label: 'Monthly', description: 'Billed on the 1st.' },    { value: 'yearly', label: 'Yearly', description: 'Two months free.' },  ]}/>
<Checkbox label="Email me receipts" description="One message per payment." defaultChecked /><Switch justified label="Reduce motion" description="Turns off transforms." />

Which one

ControlUse it when
CheckboxZero or more of several things, or a single opt-in that is saved with a form
RadioGroupExactly one of a small, visible set
SwitchA setting that applies the moment it is flipped

Do

Use a Switch only when the change takes effect immediately — that is what a switch promises.

Don't

Put a Switch in a form with a Save button; a person who flips it and leaves believes it was applied.

Segmented

RadioGroup takes variant="segmented" to draw itself as one track of adjoining cells rather than a column of dots. It is the same radiogroup: same role, same arrow keys, same value — only the drawing changes (ADR 0014).

tsx
<RadioGroup  variant="segmented"  label="Kind"  value={kind}  onValueChange={setKind}  options={[    { value: 'food', label: 'Food' },    { value: 'activity', label: 'Activity' },  ]}/>

This is the control for two to four options — the band where a Select costs a press to reveal choices that would have fitted on screen anyway. See the table on Select's page for where the boundary sits and why.

Do

Keep segmented labels to a word or two — the cells share a track and the longest one sets the width.

Don't

Use segmented past four or five options; it stops fitting and becomes a Select.

Props

PropTypeDefaultNotes
labelReactNodeRequired on all three. It is the accessible name, and it is clickable.
descriptionReactNodeSecond line, wired through aria-describedby.
checked / defaultCheckedbooleanCheckbox and Switch.
indeterminatebooleanCheckbox only — the "some of the children" state.
optionsArray<{ value, label, description?, disabled? }>RadioGroup only.
direction'column' | 'row''column'RadioGroup layout.
justifiedbooleanfalseSwitch only: label left, control right — the settings-row arrangement.
onCheckedChange / onValueChange(value) => voidOne argument, always the value.

Accessibility

  • The label is a real <label for>, so clicking the text toggles the control.
  • A description is linked with aria-describedby, not stuffed into the label.
  • A RadioGroup is a radiogroup; give it a label when it is not inside a Field.
  • The Switch thumb still moves under reduced motion — the movement is the state. Only the easing goes.