Radio
Radio — one choice out of several, built on <input type="radio">.
Examples
<fieldset class="field">
<legend class="field-label">Billing period</legend>
<div class="field field-inline">
<input class="radio" id="demo-plan-m" name="demo-plan" type="radio" value="m" checked />
<label class="field-label" for="demo-plan-m">Monthly</label>
</div>
<div class="field field-inline">
<input class="radio" id="demo-plan-y" name="demo-plan" type="radio" value="y" />
<label class="field-label" for="demo-plan-y">Yearly — two months free</label>
</div>
<div class="field field-inline">
<input class="radio" id="demo-plan-c" name="demo-plan" type="radio" value="c" disabled />
<label class="field-label" for="demo-plan-c">Custom — contact sales</label>
</div>
</fieldset>The <fieldset> and its <legend> are the component, not decoration around it.
The legend names the question; without it a screen reader reads "Monthly, radio button, 1 of 3" with nothing to attach it to. The shared name is what makes the three one group — drop it and each becomes an independent control that can never be unchecked. Both are markup, and no CSS can supply either.
<fieldset class="field">
<legend class="field-label">Delivery</legend>
<div class="field field-inline">
<input class="radio radio-lg" id="demo-ship-s" name="demo-ship" type="radio" value="s" checked />
<label class="field-label" for="demo-ship-s">Standard — 3 to 5 days</label>
</div>
<div class="field field-inline">
<input class="radio radio-lg" id="demo-ship-e" name="demo-ship" type="radio" value="e" />
<label class="field-label" for="demo-ship-e">Express — next day</label>
</div>
</fieldset>.radio-lg is 28px. There is no small size, for the reason there is no .check-sm: below 24px the control fails SC 2.5.8, and a class whose only effect is a WCAG failure is a defect rather than an option.
Keyboard behaviour is entirely the platform's — one Tab stop for the group, arrow keys between the options, wrapping at both ends — which is why this component ships no JavaScript.
Tokens 10
Level 2, declared on .radio itself. Set any of them on that selector to restyle this component without touching the skin.
| Token | Default |
|---|---|
| --radio-mark | url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='7' fill='%23000'/%3E%3C/svg%3E") |
| --radio-bg | var(--color-surface-sunken) |
| --radio-border-color | var(--color-border-strong) |
| --radio-border-width | var(--border-width) |
| --radio-checked-bg | var(--color-primary) |
| --radio-checked-border-color | var(--color-primary) |
| --radio-invalid-border-color | var(--color-danger) |
| --radio-mark-color | var(--color-on-primary) |
| --radio-mark-size | 100% |
| --radio-size | 1.5rem |
Variants and states
Variants
.radio-lg
State it reads
Read from the platform, never mirrored into a class that could disagree with it.
:checked:disabled:focus:hover:user-invalid
Before you ship it
What you have to do 4 requirements
- Give every radio in one question the same
name. That attribute is what makes them a group; without it each one is an independent control that can never be unchecked. - Wrap the group in a
<fieldset>with a<legend>. The legend names the question. Without it a screen reader reads the options with no idea what they answer, and nothing in CSS can supply one. - Label every radio —
<label for>or a wrapping<label>. - Check one by default, or accept that the group can be submitted empty. A radio group with no initial selection is a legitimate choice; a group that is
requiredand has none is the one that needs a validation message.
src/css/components/radio.css · npx mostlycss add radio