Radio
Usage
Use radios when people must choose exactly one option from a short list (up to about 6) and it helps to see all options at once — service type, priority, which bay. Use a select for longer lists, checkboxes when several answers are allowed.
- Always in a fieldset whose legend asks the question.
- Order options logically (by frequency, severity or time) and avoid preselecting an answer people must think about.
- Cards add a description per option; use them when options need explaining (service plans), not for "Yes / No".
Anatomy
- Circle —
input[type=radio].radio__input(native,appearance: none), 24px - Dot —
::before, scales in when checked - Label —
label.radio__label[for]; optional.radio__titlefor cards - Hint (optional) —
.radio__hint - Group —
fieldset.radio-groupwith legend, hint, error and.radio-group__items
Examples
Hover and focus-visible are frozen with .is-hover and .is-focus-visible, which share their rules with the real pseudo-classes.
Radio
Out of service until 14 March.
<div class="radio">
<input class="radio__input" type="radio" id="svc-scheduled" name="service" value="scheduled">
<label class="radio__label" for="svc-scheduled">Scheduled service</label>
</div>
<!-- Required radios get :user-invalid after interaction; a .field__error in the group marks the whole group -->
Radio group
<fieldset class="fieldset radio-group [radio-group--horizontal | radio-group--cards]">
<legend class="fieldset__legend">Service plan</legend>
<div class="radio-group__items">
<div class="radio">
<input class="radio__input" type="radio" id="plan-interim" name="plan" aria-describedby="plan-interim-hint">
<label class="radio__label" for="plan-interim"><span class="radio__title">Interim</span></label>
<p class="radio__hint" id="plan-interim-hint">Every 10,000 km or 6 months.</p>
</div>
…
</div>
</fieldset>
API
| Hook | Values | Purpose |
|---|---|---|
.radio, .radio__input, .radio__label, .radio__title, .radio__hint | block / parts | One option |
.radio-group (+ .fieldset), .radio-group__items | block / part | Group |
.radio-group--horizontal | modifier class | Row of 2–3 short options |
.radio-group--cards | modifier class | Bordered tiles; label stretches over the tile |
name, checked, disabled, required | attribute | Native states and grouping |
.field__error inside the group, :user-invalid | part / pseudo-class | Invalid |
.is-hover | .is-focus-visible | docs-only class | Freezes a state |
Do and don't
Accessibility
| Key | Behavior |
|---|---|
| Tab | Into the group (focuses the checked radio, or the first) and out of it — one tab stop |
| ↓ → / ↑ ← | Next / previous option, selecting it (native, wraps) |
| Space | Selects the focused radio if none is checked |
- Role: native
radioin afieldset(group) — arrow keys come from sharing aname. - Checked = filled dot inside a teal ring, not color alone; circles are 24px with a 2px border at 3:1.
- Cards: the label's
::aftercovers the tile, so the whole card is clickable; the tile shows the focus ring and a 2px selected border plus tint. - Hints are linked with
aria-describedby; the group error is linked to the fieldset.
Tokens
| Custom property | Purpose |
|---|---|
--input-border, --input-bg | Unchecked circle |
--color-action-primary-bg | -hover | Checked ring and dot |
--color-selected-bg, --color-selected-border | Selected card |
--color-border-default, --color-bg-surface, --radius-md | Card |
--color-border-invalid | Invalid |
--color-bg-muted, --color-text-disabled | Disabled |