JM Family Design System

Radio

Radio buttons capture one choice from a small set of mutually exclusive options. Use them when users benefit from seeing the options side by side.

Guidance for adoption

Start here before choosing examples or props. These notes explain where Radio helps, where another pattern is better, and what should stay true in product work.

When to use

  • Mutually exclusive choices
  • Small option sets
  • Choices that need comparison

When not to use

  • Large lists
  • Independent options
  • Actions

Accessibility expectations

  • Group related radios with fieldset and legend.
  • Support arrow-key movement within the group.
  • Expose checked and disabled states clearly.

Content guidance

  • Write the group legend as the decision or question the user is answering.
  • Keep option labels short, parallel, and mutually exclusive so comparison feels natural.
  • Use helper text only when an option needs extra context beyond its label.
  • Provide a default only when there is a safe, expected choice that does not bias the user incorrectly.

Implementation notes

  • Every radio in a group needs the same name attribute so the browser enforces one selection.
  • Wrap the group in a fieldset with a legend; there is no RadioGroup primitive today.
  • Group-level errors, required state, and shared descriptions must be composed outside the Radio component.
  • Switch to Select when the option count grows past a small, scannable set.

Import

import { Radio } from '@/components/ui/Radio';

Use inside a fieldset with a legend. Each Radio renders one option in the group.

States

Radio states should make the current selection obvious and keep every option readable. Use focus styling on the individual option that currently receives keyboard focus.

Unselected

The option is available but not currently chosen.

Selected

The option is the current choice in a mutually exclusive group.

Focused

Focus appears on the active option without moving the control.

Disabled

The option cannot be selected in the current workflow state.

Anatomy

A radio option only makes sense inside a group. The group legend names the decision; each option label names one possible answer.

Request priority

Use urgent only when the request blocks active work.

  1. Radio control
  2. Selected indicator
  3. Visible option label
  4. Group legend
  5. Optional helper text
  6. Focus and disabled state

Props

The component accepts every native HTMLInputElement attribute on top of the design system props below.

PropTypeDefaultDescription
labelReactNodeVisible label rendered beside the radio circle. Required for accessibility.
helperReactNodeSecondary text below the label. Auto-wired to aria-describedby.

Usage rules

Use for mutually exclusive choices

Radio buttons are for small sets where choosing one option automatically excludes the others.

Do not use for independent options

If users can choose more than one item, use checkboxes instead.

Show all options when comparison matters

Radio buttons are useful when users need to see the full set before deciding.

Do not use for long lists

For long or space-constrained lists, use Select or a searchable pattern when available.

Known limitations

What Radio does not do yet, and what consumers need to compose themselves.

  • No RadioGroup component. The name attribute, roving focus, required indicator, and group-level error must be wired up manually; a single Radio cannot enforce the one-of-many contract on its own.
  • No error or invalid state. There is no error prop or aria-invalid plumbing, so validation styling and messaging for radio groups must be composed externally.

Agent guidance

Agents should use radios when the user needs one answer from a small visible set. They should not use radios as status badges, filters, or independent toggles.

Generation rules

  • Use radios when users benefit from seeing all choices.
  • Provide a default only when there is a safe, expected option.
  • Do not use radios for yes/no settings when a toggle is clearer.
  • Share a single name attribute across every radio in the same group. Without it, browsers will let users pick more than one option and break the one-of-many contract.
  • Wrap the group in a fieldset with a legend that asks the question. There is no RadioGroup primitive, so the legend, roving focus, and aria-required must be wired by the consumer.
  • For group-level errors, render an Alert above the fieldset and reference it from each radio via aria-describedby. The component exposes no error or aria-invalid prop.
  • Once the option count exceeds about six, switch to a Select. Long radio stacks force the user to scroll past options they have already rejected.

Baseline example

import { Radio } from '@/components/ui/Radio';

<fieldset>
  <legend>Request status</legend>
  <Radio name="requestStatus" value="pending-review" label="Pending review" />
  <Radio name="requestStatus" value="approved" label="Approved" />
</fieldset>
If options are dynamic, numerous, searchable, or nested, agents should look for a different selection pattern instead of rendering a long radio group.

Decision standard

Radio means one answer from a visible set. If the options are independent, use Checkbox. If compactness matters more than comparison, use Select.