Radio is one option in a set, rendered as a native <input type="radio"> with a
label. The set is what holds the answer, and nothing in Radio’s own API makes a
set: value is its only required prop, and grouping happens outside it.
RadioGroupField is what you render — the group, the question above it and the
radios inside it — with Radio used only as its children.
Anatomy
Section titled “Anatomy”-
Circle the control. Selected is a dot inside it; there is no third state, because a radio can only be cleared by choosing another one. -
Label this option, in words. The question the options answer belongs to the field above the group, not here.
When to use
Section titled “When to use”Radio buttons show every alternative at once and let the reader take one. That is the trade: the set costs a row per option, and in exchange nobody has to open anything to find out what the options are. It holds for a few named alternatives, light mode or dark mode, monthly billing or annual, and stops holding as the list grows, where a Select or a Combobox trades the visibility back for the space.
Never use them for yes or no. Two radios labeled Yes and No spend two rows and two readings on a question that one Checkbox answers on submit, or a Toggle answers immediately.
Ship at least two
Section titled “Ship at least two”A radio cannot be cleared by pressing it, and nothing else in its group can clear it either: the only thing that turns a radio off is another radio in the same group turning on. A single radio is therefore a switch that goes one way and stays there, whatever the label says.
Grouping
Section titled “Grouping”A shared name is what makes the radios a group
Section titled “A shared name is what makes the radios a group”Radios belong to one group when they share a name attribute, which reaches the
input through Radio’s passthrough props. It matters because without a shared
name each radio is a group of one, and any number of them can be selected at
once.
RadioGroup is the component that sets it. It clones each child with the
group’s name, marks one checked by comparing value, wraps the set in
role="radiogroup", and stacks the options unless horizontal puts them in a
row. Keep the stack for anything past two or three short labels: stacked, every
circle sits on one left edge and the reader scans a column, where a row moves
each circle by the length of the label before it.
The cloning is conditional in a way that catches people: a child is only given
checked and onChange when it is a Radio element and the group was passed a
value or an onChange. Wrap a Radio in a layout div and the div is what gets
cloned: it receives the group’s name as a stray attribute, and the radio
inside it never reflects the group’s answer.
RadioGroupField adds the question above the options
Section titled “RadioGroupField adds the question above the options”RadioGroupField is a RadioGroup inside a FormField.
It is the one to render, because a radio group is the case where the missing
piece is not the id wiring but the question: each radio’s label names an answer,
and only the field renders the text those answers are answers to.
It forwards four props to the field — label, helperText, warningText and
errorText — and passes everything else to the group. That is narrower than its
type: inputId, labelProps, rightActions and the helper, warning and error
prop objects are accepted and then land on the group’s <div> instead of
reaching the field, where they do nothing. It has no configField at all. Pass
the four, plus name, value, onChange, horizontal and size, and treat
the rest as absent.
Hand-composing a FormField around a RadioGroup is the
way to get the props above that the field component drops, and the only reason
to do it.
Pre-select an option only where it is an answer
Section titled “Pre-select an option only where it is an answer”A pre-selected radio is indistinguishable from one the reader chose, and it is submitted the same way. Default to one when most readers want it and the rest will notice it is wrong; leave the group empty when the choice is consequential enough that “they did not answer” is worth knowing.
Accessibility
Section titled “Accessibility”Tab stops once on a radio group
Section titled “Tab stops once on a radio group”Tab reaches a radio group once and the arrow keys move within it, selecting as
they move. That comes from type="radio" and a shared name rather than from
PDS, which is the practical consequence of
keyboard behavior coming from the element: a group
built out of divs and click handlers loses it, and so does a group whose members
never got the same name, where Tab then stops on every option in turn.
The group needs a name of its own
Section titled “The group needs a name of its own”Each radio’s label names one answer and nothing names the question. A
RadioGroupField renders the question and still does not solve this: the label
it draws is a visible heading, not the group’s accessible name, because the
field never gives the group the label’s id and role="radiogroup" on a <div>
cannot be labeled by a for attribute. Set aria-label on the field, which
does reach the group, and give it the same words as the visible label.
Without it a screen reader reads four answers and never says what they answer.
Content
Section titled “Content”Ask the question once, on the group
Section titled “Ask the question once, on the group”“Which theme do you prefer?” belongs to the field; “Light”, “Dark” and “System” belong to the radios. When the question leaks into the options, every option repeats it, and the reader reads the shared part three times to find the part that differs.
The rules every label follows are on Writing.
RadioGroupField first, since it is the component you render. Read the seven
field props it accepts and drops against the section above:
| Name | Type |
|---|---|
label |
Renders |
inputId |
ID of the input element. Providing this will automatically link the label to the input. |
helperText |
Renders |
errorText |
Renders |
warningText |
Renders |
labelProps |
|
helperProps |
|
warningProps |
|
errorProps |
|
configFieldInputWidth |
Override the width of the input area when |
rightActions |
Renders actions to the right of the label. |
defaultValue |
|
name |
|
value |
|
onChange |
|
horizontal |
Direction of radio group |
size |
Size of radio buttons within the group |
ComponentPropsWithoutRef<‘div’>.Radio’s own props, for the children:
| Name | Type |
|---|---|
id |
If not provided, a unique ID will be generated. |
onChange |
|
label |
|
disabled |
|
checked |
|
valuerequired |
|
data-test |
|
size |
|
Omit<ComponentPropsWithoutRef<‘input’>, ‘size’>.Was this page helpful?