Description
Radio Button lets a user select exactly one option from a mutually exclusive set. Selecting a new option automatically deselects the previous one.
Form sections where exactly one choice is required: frequency, priority level, access role, notification preference.
When only one choice is valid, radio buttons make the constraint visible. Users can see all options simultaneously before committing to one — unlike a dropdown, which hides options behind an interaction.
A lone radio button is meaningless. Radio buttons only make sense as a group of two or more mutually exclusive options, always wrapped in a <fieldset>.
Anatomy
| Part | Required? | Notes |
|---|---|---|
| Radio control | Required | The circular input. Must meet minimum 44×44px touch target size. |
| Selected indicator | Conditional | A filled inner circle shown when the option is selected. |
| Label | Required | Describes the option. Always visible and associated via <label for> or wrapping. |
| Group container | Required | A <fieldset> wrapping the entire group. Always required — a radio button group without a fieldset is inaccessible. |
| Group label | Required | A <legend> that describes the question or category the group answers. Read by screen readers before each option. |
States
| State | Behaviour |
|---|---|
| Unselected | Default. Empty circle. |
| Selected | Circle with filled inner dot. Only one per group. |
| Disabled | Greyed out. If the whole group is disabled, disable all inputs and communicate why. |
| Focus | 2px focus ring on the radio control, --op-color-focus-ring. |
| Error | Red border on controls + error message below group when no selection is made for a required field. |
Usage guidelines
Radio Button vs Checkbox vs Dropdown
| Control | Use when |
|---|---|
| Radio button | One option from a small set (2–6). All options should be visible without interaction. Selection is mutually exclusive. |
| Checkbox | One or more options can be selected simultaneously. |
| Dropdown | One option from a larger set (7+), or when space is constrained. Options are hidden until the user opens the dropdown. |
| Switch | A single on/off setting that takes effect immediately. |
Do / Don't
Do
Pre-select a default when one option is recommended or most common. An empty radio group forces the user to make a decision they may not have needed to think about.
Don't
Don't use radio buttons for more than 6–7 options. Switch to a dropdown when the list grows long enough that comparison becomes difficult.
Do
Order options logically: alphabetically, by frequency of use, or from least to most restrictive (for permissions). Never randomise order.
Don't
Don't allow radio buttons to be deselected by clicking them again. Once selected, a radio should only change via selecting another option.
Layout & Spacing
| Element | Spec |
|---|---|
| Control size | 18×18px |
| Inner dot size | 8×8px |
| Hit area | 44×44px minimum |
| Gap (control to label) | --op-space-8 |
| Gap (between options) | --op-space-12 |
| Label font size | --op-text-sm |
| State | Border | Fill |
|---|---|---|
| Unselected | --op-color-border-strong |
--op-color-bg-primary |
| Selected | --op-color-interactive-default |
--op-color-interactive-default (inner dot) |
| Disabled | --op-color-border-default |
--op-color-bg-disabled |
| Error | --op-color-status-error |
--op-color-bg-primary |
Engineering notes
- All radio buttons in a group must share the same
nameattribute. This is what makes them mutually exclusive — the browser enforces single-selection within a name group. - Each radio must have a unique
value. Submitting a form with radio buttons requires the value to identify which option was chosen. - Don't programmatically deselect a radio by clearing
checkedwithout selecting another — users have no way to "uncheck" a radio. If "none" is a valid choice, add it as an explicit option.
Keyboard interaction
| Key | Action |
|---|---|
| Tab | Moves focus to the selected radio in the group. If none are selected, focuses the first. |
| Arrow Up / Arrow Left | Moves selection to the previous option in the group. |
| Arrow Down / Arrow Right | Moves selection to the next option in the group. |
| Space | Selects the focused radio button. |
Why it matters
Radio groups have a specific keyboard pattern that surprises many developers: the entire group is a single Tab stop, and arrow keys move between options. This is intentional — it keeps forms efficient for keyboard users. Implementing radio buttons as individual Tab stops (common in custom implementations) breaks this contract and forces keyboard users to Tab through every option instead of navigating quickly with arrows.
Required ARIA and HTML
| Pattern | Requirement |
|---|---|
| Group wrapping | Always use <fieldset> + <legend>. The legend is announced before each option, giving context. Without it, screen reader users hear "Daily — radio button" instead of "Notification frequency — Daily — radio button". |
| Required field | Mark the legend with a required indicator. Connect validation errors via aria-describedby on the fieldset. |
| Disabled state | Use the disabled HTML attribute. Don't use aria-disabled without also suppressing pointer/keyboard interaction. |
Things to avoid
- Don't use
tabindexto make individual radio buttons Tab-focusable. The arrow key navigation pattern requires the group to behave as a single Tab stop. - Don't substitute a
role="radiogroup"+role="radio"ARIA pattern when native<input type="radio">is available. The native element provides the correct keyboard behaviour with no extra scripting. - Don't visually group radios without a programmatic group container. Visual proximity is not enough — the fieldset is what communicates group membership to assistive technology.