Skip to content

Radio Button

Edit on GitHub

Radios allow the user to select a single option from a group.

@volvo-cars/css v2.5.1@volvo-cars/react-forms v2.0.1

Use RadioGroup and Radio from @volvo-cars/react-forms, or native radio inputs styled by @volvo-cars/css.

Choose your terms
Show codeHide code
import { Radio, RadioGroup } from '@volvo-cars/react-forms';
export function RadioGroupBasic() {
return (
<RadioGroup name="terms" legend="Choose your terms" defaultValue="6000">
<Radio value="6000" label="6,000 km/yr" />
<Radio value="8000" label="8,000 km/yr" />
<Radio value="unlimited" label="Unlimited km/yr" />
</RadioGroup>
);
}

Both RadioGroup and individual Radio components accept a hint prop. The group hint describes the entire set, while per-radio hints add context to individual options. Hints are linked to inputs via aria-describedby.

Pass defaultValue to RadioGroup to pre-select a radio without managing state.

Pass value and onChange to RadioGroup when you need to read or drive the selection from state.

Set errorMessage on RadioGroup to display an error and mark the group invalid. The error is hidden when the group is disabled. You can also force an invalid state with aria-invalid.

RadioGroup renders a <fieldset> with a vertical stack layout. If you need a different layout (horizontal, grid, or integrated into a custom form), use RadioContextProvider directly. Wrap your Radio components in a RadioContextProvider with a name and either defaultValue or value/onChange, and supply your own <fieldset>.

@volvo-cars/css styles native <input type="radio"> elements automatically. No class needed. Group radios inside a <fieldset> with flex-col and stack-16 for correct spacing.

Choose your model
Show codeHide code
<fieldset class="flex-col stack-16">
<legend class="mb-4 font-medium">Choose your model</legend>
<div class="flex-row">
<input type="radio" name="model" id="ex90" value="ex90" checked />
<label class="ml-8" for="ex90">Volvo EX90</label>
</div>
<div class="flex-row">
<input type="radio" name="model" id="xc90" value="xc90" />
<label class="ml-8" for="xc90">Volvo XC90</label>
</div>
<div class="flex-row">
<input type="radio" name="model" id="s90" value="s90" />
<label class="ml-8" for="s90">Volvo S90 Recharge</label>
</div>
</fieldset>

Wrap the label and hint in a flex-col stack-4 container. Use micro text-secondary for the hint text.

Set aria-invalid on the <fieldset> (not on individual radios). Include an error message with role="alert" linked via aria-describedby.

Adding any class to a radio input removes the default design system styles. To keep them while extending, include the radio class alongside your own.

Key responsibilities from the WCAG audit:

  • Label every radio: Radio requires a label prop. For CSS-only, pair each <input> with a <label htmlFor>.
  • Group with a legend: always wrap radios in a <fieldset> with a <legend>. RadioGroup handles this automatically.
  • Error on the group: set aria-invalid and aria-describedby on the <fieldset>, not on individual radios. RadioGroup wires this when errorMessage is set.
  • Hint association: use aria-describedby to link hints to their inputs. Radio does this automatically when hint is provided.
  • Pointer target size: the radio control may be smaller than the recommended 44×44px target. The adjacent label extends the clickable area but verify it meets your requirements.
  • Status messages: VoiceOver in Safari has known issues announcing error messages on radio groups. Test with multiple screen readers.

The default keyboard focus indicator wraps the radio container, including its visible label.

Radio button with keyboard focus

Radio button and label with keyboard focus.

PropTypeRequiredDefault
legendReactNode-
The legend to show above the radio group.
namestring-
The name of the radios to use when submitting the form.
hintReactNode--
Additional hint or description for the entire radio group.
childrenReactNode-
The radios.
requiredboolean--
Makes the radio group required.
onChangeChangeEventHandler<HTMLInputElement, Element>--
Fires when a radio is selected.
onInvalidFormEventHandler<HTMLFieldSetElement>--
Fires if the radio group fails validation on form submit.
disabledboolean-false
Disable all radios in the group. Use sparingly as it can be non-obvious to users why an input has been disabled. Prefer showing validation messages and hints instead.
readOnlyboolean-false
Makes the radios in the group read-only. Use sparingly, it's often preferred to present the data as regular text or in a table instead.
formstring--
Id of a form element that the radios should be associated with. Defaults to the containing form element.
enterKeyHint"done" | "go" | "next" | "previous" | "search" ...--
errorMessagestring--
Set the error message of a radio group and mark it invalid.
aria-invalidboolean-false
Force the radio group to be invalid.
valuestring--
Select the radio with this value. Either `value` or `defaultValue` must be set. Makes the radio group controlled.
defaultValuestring--
Select the uncontrolled radio with this value by default. Either `defaultValue` or `value` must be set.
hiddenboolean--
idstring--
titlestring--
dirstring--
langstring--
slotstring--
translate"yes" | "no"--
classNamestring--
styleCSSProperties--
tabIndexnumber--
onPointerDownPointerEventHandler<Element>--
onPointerEnterPointerEventHandler<Element>--
onPointerLeavePointerEventHandler<Element>--
onPointerMovePointerEventHandler<Element>--
onPointerUpPointerEventHandler<Element>--
PropTypeRequiredDefault
labelstring-
Concise label for the radio.
valuestring-on
The value that will be submitted with the form data if the radio is selected. @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/radio#data_representation_of_a_radio_group
hiddenboolean--
idstring--
titlestring--
dirstring--
langstring--
slotstring--
translate"yes" | "no"--
classNamestring--
styleCSSProperties--
tabIndexnumber--
onPointerDownPointerEventHandler<Element>--
onPointerEnterPointerEventHandler<Element>--
onPointerLeavePointerEventHandler<Element>--
onPointerMovePointerEventHandler<Element>--
onPointerUpPointerEventHandler<Element>--
disabledboolean-false
Disables the input. Use sparingly as it can be non-obvious to users why an input has been disabled. Prefer showing validation messages and hints instead.
autoFocusboolean-false
Gives the input focus on page load. Use sparingly as it can be confusing to screen-reader and mobile users.
readOnlyboolean-false
Makes the input read-only. Use sparingly, it's often preferred to present the data as regular text or in a table instead.
formstring--
Id of a form element that this input should be associated with. Defaults to the containing form element.
hintReactNode--
Additional hint or description.
enterKeyHint"done" | "go" | "next" | "previous" | "search" ...--
Hint the browser about what label to show for the Enter button on mobile keyboards.
requiredboolean-false
Makes the input required.
aria-describedbystring--
aria-labelledbystring--
onInvalidFormEventHandler<HTMLInputElement>--
Fires if the input fails validation on form submit.
onFocusFocusEventHandler<HTMLInputElement>--
onBlurFocusEventHandler<HTMLInputElement>--
onKeyDownKeyboardEventHandler<HTMLInputElement>--
onKeyUpKeyboardEventHandler<HTMLInputElement>--