A named set of two to six choices of which exactly one holds at a time, such as the theme the map is drawn in, each a round mark beside its words.
When to use
Use RadioGroup for one answer of two to six that a person reads in full before choosing, such as the theme the map is drawn in.
| Use instead | When |
|---|---|
| Select | The choices are more than fit a panel, in a short, fixed list that opens from one box |
| SearchField | The choices are many enough to need a search, such as a line or a stop |
| SegmentedControl | The choice is how one view shows its content |
| FilterChip | The choice filters the map |
| Checkbox | One choice is on or off on its own |
| CheckboxGroup | Several choices can hold at once |
| Switch | The choice is a setting, on or off, that takes effect at once |
Kongens Nytorv
Metro M1, M2 and M3.
Variants and states
| Rest | Disabled | |
|---|---|---|
| Default | Theme Match the systemLightDark | Theme Match the systemLightDark |
- RestThemeMatch the systemLightDark
- DisabledThemeMatch the systemLightDark
RadioGroup styles hover, pressed and focus on its inner parts, which a still preview can't reach, so this table shows rest and disabled only.
Props
| Name | Type | Default | Description |
|---|---|---|---|
label* | ReactNode | — | Required. The group's name, in sentence case, a word or a short question, such as "Theme": shown above the radios, and the group's accessible name. |
hiddenLabel | boolean | false | Whether the label is heard but not seen. Use it where a heading right above already names the group for the eye, so the group keeps its name for the ear. |
value | string | — | The checked radio's value, when the caller controls it. A value no radio has checks none. |
defaultValue | string | — | The radio checked at first, when the group keeps the value itself. Left out, with no value, no radio is checked until one is chosen. |
onChange | (event: Event, value: string, details: RadioGroup.ChangeEventDetails) => void | — | Called when another radio is checked, by a click, an arrow key or Space, with that radio's value; details.reason is 'none', and details.cancel() keeps an uncontrolled group as it was. Not called when the checked radio is clicked again. event is details.event. |
name | string | — | The name the checked radio's value is submitted under in a native <form>. Inside a Base UI Field.Root, the field's name wins. |
disabled | boolean | false | Whether every radio ignores the pointer and the keys. The checked radio still shows, and the group keeps one tab stop, on the checked radio or the first, announced as dimmed, that changes nothing. |
readOnly | boolean | false | Whether the group shows its choice but can't be changed. It keeps its tab stop, the arrow keys move focus without checking, and it is announced as read-only. |
required | boolean | false | Whether a radio must be checked before its form submits. Announced as required; the label says so to the eye. |
children | ReactNode | — | The group's Radios, in reading order, below the label. |
className | string | — | Reaches the root, the <div role="radiogroup">. className is merged after pn-RadioGroup and Fieldset's pn-Fieldset. |
style | CSSProperties | — | Reaches the root, the <div role="radiogroup">. className is merged after pn-RadioGroup and Fieldset's pn-Fieldset. |
ref | Ref<HTMLDivElement> | — | Reaches the root, the <div role="radiogroup">. className is merged after pn-RadioGroup and Fieldset's pn-Fieldset. |
slotProps | { legend?: FieldsetLegendProps | undefined; } | — | Props for each part: legend. |
label (required)ReactNode, default —- Required. The group's name, in sentence case, a word or a short question, such as "Theme": shown above the radios, and the group's accessible name.
hiddenLabelboolean, defaultfalse- Whether the label is heard but not seen. Use it where a heading right above already names the group for the eye, so the group keeps its name for the ear.
valuestring, default —- The checked radio's
value, when the caller controls it. A value no radio has checks none. defaultValuestring, default —- The radio checked at first, when the group keeps the value itself. Left out, with no
value, no radio is checked until one is chosen. onChange(event: Event, value: string, details: RadioGroup.ChangeEventDetails) => void, default —- Called when another radio is checked, by a click, an arrow key or Space, with that radio's
value;details.reasonis'none', anddetails.cancel()keeps an uncontrolled group as it was. Not called when the checked radio is clicked again.eventisdetails.event. namestring, default —- The name the checked radio's value is submitted under in a native
<form>. Inside a Base UIField.Root, the field's name wins. disabledboolean, defaultfalse- Whether every radio ignores the pointer and the keys. The checked radio still shows, and the group keeps one tab stop, on the checked radio or the first, announced as dimmed, that changes nothing.
readOnlyboolean, defaultfalse- Whether the group shows its choice but can't be changed. It keeps its tab stop, the arrow keys move focus without checking, and it is announced as read-only.
requiredboolean, defaultfalse- Whether a radio must be checked before its form submits. Announced as required; the label says so to the eye.
childrenReactNode, default —- The group's
Radios, in reading order, below the label. classNamestring, default —- Reaches the root, the
<div role="radiogroup">.classNameis merged afterpn-RadioGroupand Fieldset'spn-Fieldset. styleCSSProperties, default —- Reaches the root, the
<div role="radiogroup">.classNameis merged afterpn-RadioGroupand Fieldset'spn-Fieldset. refRef<HTMLDivElement>, default —- Reaches the root, the
<div role="radiogroup">.classNameis merged afterpn-RadioGroupand Fieldset'spn-Fieldset. slotProps{ legend?: FieldsetLegendProps | undefined; }, default —- Props for each part:
legend.
Use it
import { RadioGroup } from '@mylder/perron'Ask your agent
get_reference({ library: "base-ui", component: "RadioGroup" })Keyboard and ARIA
From the Base UI reference for RadioGroup and Radio and the installed RadioGroup, RadioRoot, the composite root they build on and useButton. "From" says who does it.
| Key | Does | From |
|---|---|---|
Tab | Moves focus into the group, to its one tab stop: the checked radio, or the first enabled one while none is checked; the next Tab leaves the group. A disabled group, or a checked radio disabled alone, keeps that stop, announced as dimmed, where Space changes nothing; from a checked radio disabled alone the arrow keys move on to the enabled radios | Base UI |
Shift+Tab | Moves focus out of the group, backwards | Base UI |
ArrowDown, ArrowRight | Moves focus to the next enabled radio and checks it, calling onChange; wraps from the last to the first. Under an rtl DirectionProvider, which Perron doesn't set up, ArrowRight goes back. Read-only: moves focus only | Base UI |
ArrowUp, ArrowLeft | Moves focus to the previous enabled radio and checks it, calling onChange; wraps from the first to the last. Read-only: moves focus only | Base UI |
Space | Checks the focused radio, calling onChange, if it isn't checked already; nothing while read-only or disabled | Base UI, through the hidden input |
Enter | Nothing: the installed Radio.Root stops it, as a native radio does | Base UI |
Home and End do nothing (the installed group turns enableHomeAndEndKeys off), and the component adds no keys of its own.
| Element | Role | Accessible name from | States announced |
|---|---|---|---|
root | radiogroup, from Base UI, on a <div> | aria-labelledby, the legend's id, from Base UI through Fieldset: the label's text, seen or hidden | aria-disabled, aria-readonly, aria-required; in an invalid Field, aria-invalid, with the Field's description and error as its description through aria-describedby |
legend | none: a <div>, or a <span> when hidden | none: its text names the root | none |
radio | radio, from Base UI, on a <span> | Its content: the label's text, the caller's children; the indicator holds no text | aria-checked; aria-disabled; aria-invalid in an invalid Field; its place in the group, "2 of 3" |
indicator | none: a <span> with no content | none | none |
input | none: aria-hidden="true", tabIndex −1 | none | none |
What axe won't catch, from the contract, each with how this component meets it:
- Everything the eye is told, the ear is told, for every shape each prop accepts: the checked radio's filled circle and dot are
aria-checked="true"and every other radio's"false"; a disabled radio's tertiary colours arearia-disabled, heard as dimmed, on each radio of a disabled group; a read-only group isaria-readonly; an invalid radio's danger edge isaria-invalid, with the Field's message as its description; the label the eye reads above the radios is the group's name, and a hidden label still is. - Every landmark and named region has an accessible name: the root is a
radiogroup, not a landmark, and is always named by itslabel, which is required (CR-2). - Every default label can be overridden: none here; the component owns no strings.
- Every shape the type accepts renders, in every variant:
labeland a radio'schildrenrender a string, an element and a list alike:Defaultnames its group with a string, andLabelShapeswith a list and, in itsplaytest, an element, each heard as one run, over radios whose words are a string, an element and a list; a radio's words sit in one span, so text and elements flow and wrap as one run beside the mark. - A hidden copy kept for animation is
aria-hidden, and a collapsed region isinert: the hidden inputs are Base UI's,aria-hiddenand out of the tab order; the indicator kept mounted while unchecked holds no text, so it adds nothing to the tree; nothing collapses. - Native semantics first: Base UI's
radiogroupandradioroles witharia-checked, with a native<input type="radio">beside each for the form; withrenderandnativeButton, a native<button>. Perron adds no ARIA. - Focus moves in reading order and returns on close: the arrow keys move through the radios in document order, below their label, and Tab leaves the group after its one stop; focus stays on the radio it checks; the group opens nothing to return from.
- Every target is at least
size.target-minsquare: the smallest target is a radio with a one-letter label,size.target-min(24 px) tall and the 16 px circle, thespace.200gap and the letter (at least 24 px) wide. - Loops stop when the system asks for less motion: no loop, and no transition; a radio's state changes at once.
Owned strings
No owned strings: every word RadioGroup shows or announces comes from the caller, through label and each radio's children. Base UI's roles say checked or not checked, and a radio's place in its group, in every locale with no word of Perron's.
Do and don't
For a caller using the component, with the reason. Each row is a rule: a must row's Don't is never right, a should row's needs a reason, and Checked by says what catches the Don't in a caller's code.
| Rule | Level | Do | Don't | Checked by |
|---|---|---|---|---|
| DD-1 | should | Use a RadioGroup for one answer of two to six that a person reads in full before choosing, such as the theme or the language | Use it for more choices than fit a panel, or for choices that need a search: a select or a search field keeps a long list out of the way | review |
| DD-2 | should | Switch how one view shows its content with a SegmentedControl, and filter the map with a FilterChip | Use a row of radios for a view's mode or the top bar's filters: a radio is a form answer, read down a column | review |
| DD-3 | must | Name the group with label, in sentence case, a word or a short question: "Theme" | Leave out the label, or name the group with aria-label where there is room to show one: a sighted person can't tell what an unnamed set of choices answers | review |
| DD-4 | must | Write each radio's words as a short noun phrase in sentence case, of a similar length and shape: "Match the system", "Light", "Dark" | Put a link or a button inside a radio's words: a radio's content is presentational, so a screen reader never reaches it, and a click on it checks the radio too | review |
| DD-5 | should | Check the safest or most common choice with defaultValue, or leave all unchecked with required when the person must decide | Check a choice the person never made and then treat it as their answer: a pre-checked radio is easy to pass by | review |
| DD-6 | should | Disable the whole group with disabled, or one radio that can't be chosen now, and say why in the label or the Field's description | Disable a radio with no word of why, or hide it: a dimmed choice can't explain itself, and a missing one moves the others | review |
| DD-7 | must | Give a hint or an error through Base UI's Field.Root round the group, with Field.Description and Field.Error and no Field.Label | Put a hint or an error as plain text among the radios: nothing ties it to the group for the ear, and the arrow keys never reach it | review |
| DD-8 | must | Name the group with its own label | Wrap the group in Perron's Field: its label renames every radio in it, so each radio is heard as the field's label, never its own words | review |
| DD-9 | should | Use a checkbox for a choice that can be on or off on its own, or several at once | Use a RadioGroup of one radio, or of "Yes" and "No" for a setting that takes effect at once: one radio can't be unchecked, and a setting that applies now is a switch | review |