Description
What it does
Radio Button lets a user select exactly one option from a mutually exclusive set. Selecting a new option automatically deselects the previous one.
Where it appears
Form sections where exactly one choice is required: frequency, priority level, access role, notification preference.
Why it exists
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 | 12 / 14 / 16 / 20 / 24px (XS to XL), medium 16px |
| Inner dot size | 8 / 8 / 8 / 12 / 16px. --orbit-selection-control-size-*-dot |
| Hit area | 44×44px minimum |
| Gap (control to label) | 4 / 8 / 8 / 10 / 12px by size |
| Gap (between options) | 8 / 10 / 12 / 16 / 24px by size. --orbit-radio-group-size-*-gap |
| Label font size | Same as the control size |
| Size | Control | Label | Gap |
|---|---|---|---|
| Extra small | 12px | 12px | 4px |
| Small | 14px | 14px | 8px |
| Medium (default) | 16px | 16px | 8px |
| Large | 20px | 20px | 10px |
| Extra large | 24px | 24px | 12px |
The control is the same size as its label text. Values come from --orbit-selection-control-size-{xs|s|m|l|xl}-{control|font-size|gap}; a 44×44px hit area is still required around the smaller sizes.
Component tokens
Checkbox, radio and switch share the --orbit-selection-control-* tokens, so they are framework-neutral CSS variables that any framework (including React Aria components) can use without Web Awesome. Colours follow Light and Dark mode and the product theme, and every token aliases a Semantic token.
| Token | What it sets |
|---|---|
--orbit-selection-control-background, -border |
Unchecked fill and outline. The border meets 3:1 against the surface. |
--orbit-selection-control-checked-background, -checked-border, -checked-icon |
Checked fill, outline and the check mark or dot. |
--orbit-selection-control-state-hover |
Translucent overlay for the hover state. |
--orbit-selection-control-invalid-border, -invalid-message |
Outline and message colour when invalid. |
--orbit-selection-control-disabled-background, -border, -checked-background, -checked-icon, -text |
Disabled colours. |
--orbit-selection-control-label-color, -label-font-weight, -hint-color, -hint-font-size |
Label and hint text. |
| `--orbit-selection-control-focus-ring-{color | width |
--orbit-selection-control-radius, -border-width |
Corner radius and outline width. |
States are Default, Hover, Invalid and Disabled. Focus is shown with the focus ring.
Radio button appearance
The button appearance draws each option as a bordered, button-like segment. A group of them is one connected control: no gap, shared single borders, and rounded corners only on the outer ends (--orbit-radio-group-radius). Heights match Button and the form fields (24 / 32 / 40 / 48 / 56px) through --orbit-radio-button-size-*-height and -padding-inline. Selected segments use --orbit-radio-button-selected-background and -selected-border.
| 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.