EDS 0.x
Components

Radio

Examples

Sizes

Figma size sets: Sm 16px · Md 20px.

States

Interaction states from Figma: Default, Hover, Focus, Disabled, Read-only — across Unchecked and Checked.

Validation

Figma Validation=Invalid (Negative) — use validation="error". Unchecked uses a light negative fill; checked uses solid negative.

Radio with Label

RadioOption is Figma Radio.Option — label required, optional description.

Option Group — Vertical

Option Group — Horizontal

Controlled

Selected: email

TanStack Form

Submit without a selection to see validation. Choosing a plan and submitting again succeeds.

Plan

RTL

Wrap the control (or a parent) in dir="rtl" / lang="fa". Option labels and descriptions align to the inline-start edge.

API Reference

Radio

PropTypeDefaultDescription
valuestringUnique value within the group (required)
size"sm" | "md""sm"Box size — Sm 16px / Md 20px
validation"none" | "error""none"Figma Validation (error = Invalid Negative)
disabledbooleanfalseDisables interaction
readOnlybooleanfalsePrevents selection (disabled surface)
requiredbooleanfalseMarks as required for form validation
inputRefRef<HTMLInputElement>Ref to the hidden native radio input
classNamestringExtra classes

RadioOption

PropTypeDefaultDescription
labelReactNodeLabel text (required)
descriptionReactNodeHelper text below the label
valuestringUnique value within the group (required)
All Radio propsPassed through to the inner Radio

RadioOptionGroup

PropTypeDefaultDescription
direction"vertical" | "horizontal""vertical"Layout direction
valuestringControlled selected value
defaultValuestringInitial value (uncontrolled)
onValueChange(value: string) => voidCalled when selection changes
namestringForm field name
inputRefRef<HTMLInputElement>Ref to the group’s hidden native input
disabledbooleanfalseDisables all radios in the group
readOnlybooleanfalsePrevents changing selection
requiredbooleanfalseMarks the group as required

Accessibility

WCAG success criteria

CriterionLevelHow EDS helps
2.1.1 KeyboardAArrow keys move selection within the group; Space selects; Tab enters/leaves the group
2.4.7 Focus VisibleAA:focus-visible ring with 2px offset outside the control
1.3.1 Info and RelationshipsAradiogroup relationship plus labeled options communicate the single-choice set
4.1.2 Name, Role, ValueANamed radiogroup, radio roles, aria-checked, and shared name — verify group + option names in QA
1.4.3 Contrast (Minimum)AALabel and description text use default foreground tokens
1.4.11 Non-text ContrastAACircle border, selected fill, dot, and error styling meet 3:1 against adjacent colors
3.3.1 Error IdentificationAvalidation="error" on options plus consumer-provided error text
3.3.2 Labels or InstructionsARadioOption requires label; groups require aria-label / aria-labelledby
2.5.8 Target Size (Minimum)AARadioOption label enlarges the hit target; avoid bare radios without labels in touch layouts

Why it matters

Radio groups force a single choice — billing plans, shipping methods, consent levels. When the group is unnamed, arrow keys do nothing useful, or focus disappears after selection, users cannot complete forms without a mouse. Screen-reader users need the group name, the selected option, and only one checked item announced reliably. Getting radiogroup semantics wrong is one of the most common 4.1.2 failures in product audits.

Keyboard

KeyContextBehavior
⇥ Tab / + ⇥ TabPage / formEnters or leaves the group — focus lands on the currently selected radio, or the first enabled option if none is selected
/ Vertical RadioOptionGroupMoves selection to the previous/next enabled option (roving focus within the group)
/ Horizontal RadioOptionGroupMoves selection to the previous/next enabled option
SpaceFocused, enabled radioSelects the focused option (if not already selected)
Space / Arrow keysdisabled group or optionNo change — disabled options are skipped in arrow navigation
Space / Arrow keysreadOnly groupNo change — options stay focusable but selection cannot move

Arrow keys operate within the group only; Tab moves between groups and other controls. Only one radio per group may be selected at a time.

Screen readers & semantics

SurfaceSemanticsNotes
RadioOptionGrouprole="radiogroup" via Base UI RadioGroupMust have aria-label or aria-labelledby naming the set (e.g. “Plan”, “Notifications”)
Radio / RadioOptionrole="radio" with aria-checkedExactly one true per group; others false
RadioOption<label> wrapping control + textVisible label supplies the accessible name for each option
Bare RadioRequires aria-label or aria-labelledbyUse inside a named group when no visible label exists
name on groupShared native form nameEnsures correct submission and groups options for assistive tech
inputRefHidden native <input type="radio">Enables programmatic focus / scroll-to-field from TanStack Form
Dot indicatoraria-hidden="true"Decorative — selection state comes from the radio role
validation="error"Visual styling onlyApply to each option (or the group’s options) and pair with role="alert" error text

For 4.1.2 Name, Role, Value, verify three things in audits: the radiogroup has a name, each radio has a name, and the checked state updates when arrow keys or Space change selection.

Focus & visuals

  • Focus ring: :focus-visible on each radio draws a 2px gap + 2px stroke outside the circle via ::after, using --eds-color-outline-primary (overridable with --eds-component-radio-focus-ring).
  • Roving focus: Base UI keeps DOM focus on the active radio while arrow keys move selection — the focus ring follows the current choice.
  • Contrast: Selected fill and dot use semantic tokens; error (validation="error") states use negative tokens for 3:1 non-text contrast against adjacent backgrounds.
  • Target size: RadioOption expands the clickable region via its <label>; bare radios should sit in layouts with adequate spacing or an associated label.
  • RTL: Options lay out with logical properties; arrow keys follow document reading order (inline axis), so ←/→ still move between options correctly in dir="rtl".

Build with it

Do

  • Always wrap options in RadioOptionGroup — a lone Radio still needs a group ancestor for roving focus and radiogroup semantics.
  • Name the group with aria-label or aria-labelledby before shipping.
  • Set name on RadioOptionGroup for native form posts and assistive tech grouping.
  • Pass inputRef on the group (or option) when TanStack Form needs to focus the field after validation failure.
  • Apply validation="error" to options and render descriptive error copy with role="alert".
  • Use readOnly (not disabled) when the selected value must remain visible and focusable but not changeable.

Don't

  • Render multiple independent <Radio /> elements without a RadioOptionGroup — arrow keys and radiogroup naming will break.
  • Rely on legend text alone without wiring aria-labelledby to the group.
  • Use separate radio groups for what is logically one question (splits keyboard navigation and confuses screen readers).
  • Disable the entire group to show a read-only answer — use readOnly on the group instead.

Quick test

  1. Keyboard-only (2 min): Tab into a vertical group; use ↓/↑ to move — only one option stays selected; Space confirms; Tab out reaches the next control.
  2. VoiceOver / NVDA (2 min): Confirm the group name is announced on entry; each option reads “radio button, checked/unchecked, N of M”; selection updates when arrows move.
  3. 4.1.2 spot check (1 min): Inspect the DOM — one radiogroup with an accessible name, each option a radio with its own name, exactly one aria-checked="true".
  4. Form error (1 min): Submit the TanStack Form demo empty — error text appears, options show error styling, refocus lands on the field when using inputRef.
  5. RTL (30 sec): Set dir="rtl" on a horizontal group — labels align inline-start and arrow keys still move between options in reading order.

On this page