Skip to content

Radio Group

Form InputsSince 0.2.0
import { RadioGroup } from '@absoluteui/components/radio-group';
import '@absoluteui/components/radio-group/style.css';

Using Angular? See Radio Group for Angular.

Exactly one choice from a short set of mutually exclusive options. RadioGroup renders a <fieldset role="radiogroup"> and <legend> around native radios that share one name (generated when name is absent), so the browser provides the single tab stop and arrow-key selection. It adds group help text, required validation, and one localized validation message.

  • Choosing one option from 2 to about 7 visible options where comparing them matters.
  • A required choice with no sensible default.
  • Several independent choices: use Checkbox Group.
  • Long or searchable lists: use Select or Combobox.
  • A binary setting that applies immediately: use Switch.
import { RadioGroup } from '@absoluteui/components/radio-group';
const host = document.createElement('div');
document.body.append(host);
const shipping = RadioGroup.create(host, {
label: 'Shipping speed',
name: 'shipping',
required: true,
options: [
{ value: 'standard', label: 'Standard', description: '3 to 5 business days' },
{ value: 'express', label: 'Express', description: 'Next business day' },
],
});
host.addEventListener('aui-change', () => {
host.dataset.shipping = shipping.value ?? '';
});
import { RadioGroup } from '@absoluteui/components/radio-group';
const host = document.createElement('div');
document.body.append(host);
RadioGroup.create(host, {
label: 'Size',
orientation: 'horizontal',
value: 'm',
options: [
{ value: 's', label: 'Small' },
{ value: 'm', label: 'Medium' },
{ value: 'l', label: 'Large', disabled: true },
],
});
import { configureAbsoluteUI } from '@absoluteui/core';
configureAbsoluteUI({ components: { radioGroup: { validationTiming: 'submit' } } });
import { RadioGroup } from '@absoluteui/components/radio-group';
import '@absoluteui/components/radio-group/style.css';
const host = document.querySelector<HTMLElement>('#size');
if (host) {
const size = RadioGroup.create(host, {
label: 'Size',
orientation: 'horizontal',
options: [
{ value: 's', label: 'Small' },
{ value: 'm', label: 'Medium' },
{ value: 'l', label: 'Large' },
],
});
host.addEventListener('aui-change', () => {
host.dataset.size = size.value ?? '';
});
}
import { RadioGroup } from '@absoluteui/components/radio-group';
const host = document.createElement('div');
document.body.append(host);
RadioGroup.create(host, {
label: 'Shipping speed',
name: 'shipping',
required: true,
options: [
{ value: 'standard', label: 'Standard' },
{ value: 'express', label: 'Express' },
],
});
<div class="aui-field aui-radio-group" data-aui-control> (consumer host)
<fieldset class="aui-radio-group__fieldset" role="radiogroup" aria-labelledby=legend aria-required aria-invalid aria-describedby>
<legend class="aui-field__label">Label<span class="aui-field__required" aria-hidden="true">*</span></legend>
<div class="aui-field__description"> (optional)
<div class="aui-radio-group__options">
<div class="aui-radio-group__option"> (per option)
<span class="aui-radio-group__box">
<input type="radio" class="aui-radio-group__input" name value>
<span class="aui-radio-group__dot" aria-hidden="true">
</span>
<div class="aui-radio-group__text">
<label class="aui-radio-group__label" for=input>
<div class="aui-radio-group__option-description"> (optional)
</div>
</div>
</div>
<div class="aui-field__error aui-validation-message"> (when shown)
</fieldset>
</div>
RadioGroup.create(host: HTMLElement, options: RadioGroupOptions): RadioGroup

Global configuration key: radioGroup (see Configuration).

Option Type Default Description
label string Legend text and accessible name of the radiogroup. Required.
hideLabel boolean false Visually hide the legend; it remains the accessible name.
options readonly { value: string; label: string; description?: string; disabled?: boolean }[] Choices; values must be unique. Required.
value string | null "null" Initially selected value; also restored by reset().
name string Native radio name; a unique name is generated when absent.
description string Group help text referenced by the radiogroup’s aria-describedby.
required boolean false A selection is required (aria-required on the radiogroup, native required on radios).
orientation 'vertical' | 'horizontal' "vertical" Layout of the options.
disabled boolean false Disables the whole group (fieldset disabled).
validate (value: string | null) => string | null Consumer validation; return a message to mark the selection invalid.
icons Partial<{ error: IconRef }> Icon slot overrides.
validationTiming 'blur' | 'input' | 'submit' "blur" When validation messages first appear; afterwards they update live. Globally configurable.
locale string "en-US" Message locale. Globally configurable. Inherited from common configuration.
Property Type Description
host HTMLElement Consumer host; its children are owned by the component.
fieldset HTMLFieldSetElement The fieldset with role=radiogroup.
inputs HTMLInputElement[] The native radios, in option order.
value string | null Selected value, or null.
name string | null Consumer form field name (null when generated).
label string Legend text.
disabled boolean Group disabled state.
validity FieldValidity { valid, error, message }; computed on every change.
dirty boolean Selection differs from the initial selection.
touched boolean Focus has left the group at least once.
options Readonly<RadioGroupOptions> Current instance options.
Method Description
setValue(value: string | null): void Programmatic selection (no events); null clears it; unknown values throw RADIO_GROUP_OPTIONS.
update(options: Partial<RadioGroupOptions>): void Merge and apply options; a new option list keeps the selection if it still exists.
reportValidity(): boolean Show messages now and return validity.
setCustomError(message: string | null): void External error; cleared when the selection changes.
getFormValue(): string | null Selected value for form serialization.
focus(): void Focus the selected radio, else the first enabled radio.
reset(): void Restore the initial selection and clear dirty/touched/submitted state.
destroy(): void Remove rendering, listeners, registration, and subscriptions.
Event Detail Bubbles Cancelable Description
aui-change { value: string | null; validity: FieldValidity } Yes No The user selected an option (click, Space, or arrow keys).

All rules live in the aui.components cascade layer, so application styles override them without specificity battles.

Class Purpose
aui-field aui-radio-group Host.
aui-radio-group--invalid | --disabled State modifiers on the host.
aui-field__label | __required | __description | __error Shared field chrome (the label is the legend).
aui-radio-group__fieldset Fieldset with role=radiogroup.
aui-radio-group__options | __options--horizontal Option list and its horizontal layout.
aui-radio-group__option One option row.
aui-radio-group__box | __input | __dot Radio wrapper, native input, and selected dot.
aui-radio-group__text | __label | __option-description Option label and help text.
Slot Default icon
error alert-circle

Tokens this stylesheet reads (verified in CI). Override them in a theme or with the Theme Designer.

--aui-border-width | --aui-color-border | --aui-color-border-strong | --aui-color-disabled-bg | --aui-color-error-fg | --aui-color-focus-ring | --aui-color-on-primary | --aui-color-primary | --aui-color-surface | --aui-color-text | --aui-color-text-disabled | --aui-color-text-muted | --aui-duration-fast | --aui-easing-standard | --aui-focus-ring-offset | --aui-focus-ring-width | --aui-font-family-sans | --aui-font-size-md | --aui-font-size-sm | --aui-font-weight-medium | --aui-icon-size-md | --aui-icon-size-sm | --aui-line-height-normal | --aui-line-height-tight | --aui-space-1 | --aui-space-2 | --aui-space-4 | --aui-touch-target-min

type RadioGroupOrientation = 'vertical' | 'horizontal';
interface RadioGroupOption { value: string; label: string; description?: string; disabled?: boolean }
interface RadioGroupIcons { error: IconRef }
interface RadioGroupChangeDetail { value: string | null; validity: FieldValidity }

Pattern: Native radios sharing one name inside <fieldset role=“radiogroup”> + <legend> (WAI-ARIA APG Radio Group, native keyboard model).

Key Context Action
Tab / Shift+Tab Any Moves focus into the group (selected radio, else the first enabled radio) and out of it; the group is one tab stop.
ArrowDown / ArrowRight Radio focused Moves to and selects the next enabled radio, wrapping (native).
ArrowUp / ArrowLeft Radio focused Moves to and selects the previous enabled radio, wrapping (native).
Space Unselected radio focused Selects the focused radio (native).
Element Role
fieldset radiogroup (explicit), named by the legend via aria-labelledby
input radio (native)
selected dot and icons none (aria-hidden=true)
Attribute Element Value
aria-labelledby fieldset Legend id
aria-required fieldset “true” when required
aria-invalid fieldset “true” only while an error message is shown
aria-describedby fieldset Group help text and visible error message ids
aria-describedby input That option’s help text id, when present

The native radio is the focus target and shows a color-focus-ring outline on :focus-visible.

Criterion Level Note
1.3.1 Info and Relationships A
1.4.1 Use of Color A
1.4.3 Contrast (Minimum) AA
1.4.11 Non-text Contrast AA
2.1.1 Keyboard A
2.4.6 Headings and Labels AA
2.4.7 Focus Visible AA
2.5.3 Label in Name A
3.3.1 Error Identification A
3.3.2 Labels or Instructions A
4.1.2 Name, Role, Value A

Automated test results: Accessibility and compliance.

  • The required asterisk is aria-hidden; aria-required on the radiogroup and native required on the radios convey the state.
  • Selection is shown by an inner dot, not only by the fill color.
  • Arrow-key navigation is native: disabled radios are skipped and selection follows focus.
  • Errors appear after focus leaves the group by default, not when moving between radios.
  • Screen-reader announcement of aria-invalid and aria-describedby on the radiogroup has not yet been manually verified with NVDA, JAWS, and VoiceOver.

default, hover, focus-visible, selected, disabled (group), disabled (option), required, invalid (message shown).

From Trigger To
any click, label click, Space, or arrow key new selection; aui-change; dirty
any focus leaves the group touched; messages shown (blur timing)
any reportValidity() submitted; messages shown for every timing
invalid (custom error) selection changes custom error cleared
any reset() initial selection; pristine, untouched
  • The required asterisk is aria-hidden; aria-required conveys the state.
  • Selection is shown by an inner dot, not only color.
  • Native keyboard behavior is used, so it matches platform conventions and skips disabled radios.
  • Errors appear after focus leaves the whole group, not when moving between radios.
  • Manual screen-reader verification is pending (see Known limitations).

Vertical by default. orientation: 'horizontal' wraps options in a row. Labels and help text wrap; rows are at least touch-target-min tall.

Logical properties and flex layout: radios are at the inline start and horizontal options flow from the inline start. Arrow keys follow the browser’s native direction handling.

Colors come from role tokens only. In forced-colors mode rings use Canvas/CanvasText, the selected ring and dot use Highlight, and disabled uses GrayText, with forced-color-adjust: none.

configureAbsoluteUI({ components: { radioGroup: { validationTiming: 'submit' } } });

Changes apply to existing instances immediately.

Instance options override global and theme defaults. All options are dynamic; changing options or name rebuilds the radios.

icons.error (default alert-circle). The selected dot is a CSS shape, not an icon.

All text uses textContent. Validation messages never include the selected value. CSP-safe.

No persistence, network access, or telemetry. With name, the selected radio submits name=value. Without name, a generated name groups the radios; it is not tied to user data.

Two listeners per instance (delegated change, focusout), a shared document-level pointer tracker, and one configuration subscription. Rendering is linear in the number of options. Target: under 2 ms for 7 options; not yet covered by the performance test.

Short lists, up to about 20 options. Unit-tested with 4 options.

  • Once selected, the user cannot clear the selection (native); add a “None” option if needed.
  • Two groups with the same consumer name in one form become one native group.
  • update({ options }) clears the selection when its value no longer exists.
  • A disabled selected option stays selected but is skipped by arrow keys.
Code Category Cause
RADIO_GROUP_OPTIONS configuration Missing label, empty or duplicate options, unknown value, empty name, invalid orientation.
Validation (validity.error) validation value-missing (“Select an option.”), custom.

A failed construction leaves no rendering, listeners, or subscriptions behind.

  • The selection cannot be cleared by the user.
  • Screen-reader behavior of aria-invalid and descriptions on the radiogroup has not yet been manually verified with NVDA, JAWS, and VoiceOver.
  • HTML in labels or the legend.
  • Custom roving focus.
  • Read-only radio groups.