useRadioGroup
Provides the behavior and accessibility implementation for a radio group component. Radio groups allow users to select a single item from a list of mutually exclusive options.
install | yarn add @react-aria/radio |
---|---|
version | 3.0.0-rc.3 |
usage | import {useRadioGroup, useRadio} from '@react-aria/radio' |
API#
useRadioGroup(
(props: AriaRadioGroupProps,
, state: RadioGroupState
)): RadioGroupAria
useRadio(
props: RadioAriaProps,
state: RadioGroupState,
ref: RefObject<HTMLElement>
): RadioAria
Features#
Radio groups can be built in HTML with the
<fieldset>
and <input> elements,
however these can be difficult to style. useRadioGroup
and useRadio
help achieve accessible
radio groups that can be styled as needed.
- Radio groups are exposed to assistive technology via ARIA
- Each radio is built with a native HTML
<input>
element, which can be optionally visually hidden to allow custom styling - Full support for browser features like form autofill
- Keyboard focus management and cross browser normalization
- Group and radio labeling support for assistive technology
Anatomy#
A radio group consists of a set of radio buttons, and a label. Each radio includes a label and a visual selection indicator. A single radio button within the group can be selected at a time. Users may click or touch a radio button to select it, or use the Tab key to navigate to the group, the arrow keys to navigate within the group, and the Space key to select an option.
useRadioGroup
returns props for the group and its label, which you should spread
onto the appropriate element:
Name | Type | Description |
radioGroupProps | HTMLAttributes<HTMLElement> | Props for the radio group wrapper element. |
labelProps | HTMLAttributes<HTMLElement> | Props for the radio group's visible label (if any). |
useRadio
returns props for an individual radio:
Name | Type | Description |
inputProps | InputHTMLAttributes<HTMLElement> | Props for the input element |
Selection state is managed by the useRadioGroupState
hook in @react-stately/radio
. The state object should be passed as an option to useRadio
.
Individual radio buttons must have a visual label. If the radio group does not have a visible label,
an aria-label
or aria-labelledby
prop must be passed instead to identify the element to assistive
technology.
Example#
This example uses native input elements for the radios, and React context to share state from the group
to each radio. An HTML <label>
element wraps the native input and the text to provide an implicit label
for the radio.
import {useRadioGroupState} from '@react-stately/radio';
let RadioContext = ReactcreateContext();
function RadioGroup(props) {
let {children label} = props;
let state = useRadioGroupState(props);
let {radioGroupProps labelProps} = useRadioGroup(props state);
return (
<div ...radioGroupProps>
<span ...labelProps> label</span>
<RadioContextProvider value= state>
children
</RadioContextProvider>
</div>
)
}
function Radio(props) {
let {children} = props;
let state = ReactuseContext(RadioContext);
let {inputProps} = useRadio(props state);
return (
<label style={display: 'block'}>
<input ...inputProps />
children
</label>
);
}
<RadioGroup label="Favorite pet">
<Radio value="dogs">Dogs</Radio>
<Radio value="cats">Cats</Radio>
</RadioGroup>
import {useRadioGroupState} from '@react-stately/radio';
let RadioContext = ReactcreateContext();
function RadioGroup(props) {
let {children label} = props;
let state = useRadioGroupState(props);
let {radioGroupProps labelProps} = useRadioGroup(
props
state
);
return (
<div ...radioGroupProps>
<span ...labelProps> label</span>
<RadioContextProvider value= state>
children
</RadioContextProvider>
</div>
);
}
function Radio(props) {
let {children} = props;
let state = ReactuseContext(RadioContext);
let {inputProps} = useRadio(props state);
return (
<label style={display: 'block'}>
<input ...inputProps />
children
</label>
);
}
<RadioGroup label="Favorite pet">
<Radio value="dogs">Dogs</Radio>
<Radio value="cats">Cats</Radio>
</RadioGroup>
import {useRadioGroupState} from '@react-stately/radio';
let RadioContext = ReactcreateContext();
function RadioGroup(
props
) {
let {
children
label
} = props;
let state = useRadioGroupState(
props
);
let {
radioGroupProps
labelProps
} = useRadioGroup(
props
state
);
return (
<div
...radioGroupProps>
<span
...labelProps>
label
</span>
<RadioContextProvider
value= state>
children
</RadioContextProvider>
</div>
);
}
function Radio(props) {
let {children} = props;
let state = ReactuseContext(
RadioContext
);
let {
inputProps
} = useRadio(
props
state
);
return (
<label
style={
display: 'block'
}>
<input
...inputProps
/>
children
</label>
);
}
<RadioGroup label="Favorite pet">
<Radio value="dogs">
Dogs
</Radio>
<Radio value="cats">
Cats
</Radio>
</RadioGroup>
Styling#
To build a custom styled radio button, you can make the native input element visually hidden.
This is possible using the <VisuallyHidden
>
utility component from @react-aria/visually-hidden
. It is still in the DOM and accessible to
assistive technology, but invisible. This example uses SVG to build the visual radio button,
which is hidden from screen readers with aria-hidden
.
For keyboard accessibility, a focus ring is important to indicate which element has keyboard focus.
This is implemented with the useFocusRing
hook from @react-aria/focus
. When isFocusVisible
is true, an extra SVG element is rendered to
indicate focus. The focus ring is only visible when the user is interacting with a keyboard,
not with a mouse or touch.
import {VisuallyHidden} from '@react-aria/visually-hidden';
import {useFocusRing} from '@react-aria/focus';
// RadioGroup is the same as in the previous example
let RadioContext = ReactcreateContext();
function RadioGroup(props) {
let {children label} = props;
let state = useRadioGroupState(props);
let {radioGroupProps labelProps} = useRadioGroup(props state);
return (
<div ...radioGroupProps>
<span ...labelProps> label</span>
<RadioContextProvider value= state>
children
</RadioContextProvider>
</div>
)
}
function Radio(props) {
let {children} = props;
let state = ReactuseContext(RadioContext);
let {inputProps} = useRadio(props state);
let {isFocusVisible focusProps} = useFocusRing();
let isChecked = inputPropschecked;
let strokeWidth = isChecked ? 6 : 2;
return (
<label style={display: 'flex' alignItems: 'center'}>
<VisuallyHidden>
<input ...inputProps ...focusProps />
</VisuallyHidden>
<svg
width=24
height=24
aria-hidden="true"
style={marginRight: 4}>
<circle
cx=12
cy=12
r=8 - strokeWidth / 2
fill="none"
stroke= isChecked ? 'orange' : 'gray'
strokeWidth= strokeWidth />
isFocusVisible &&
<circle
cx=12
cy=12
r=11
fill="none"
stroke="orange"
strokeWidth=2 />
</svg>
children
</label>
);
}
<RadioGroup label="Favorite pet">
<Radio value="dogs">Dogs</Radio>
<Radio value="cats">Cats</Radio>
</RadioGroup>
import {VisuallyHidden} from '@react-aria/visually-hidden';
import {useFocusRing} from '@react-aria/focus';
// RadioGroup is the same as in the previous example
let RadioContext = ReactcreateContext();
function RadioGroup(props) {
let {children label} = props;
let state = useRadioGroupState(props);
let {radioGroupProps labelProps} = useRadioGroup(
props
state
);
return (
<div ...radioGroupProps>
<span ...labelProps> label</span>
<RadioContextProvider value= state>
children
</RadioContextProvider>
</div>
);
}
function Radio(props) {
let {children} = props;
let state = ReactuseContext(RadioContext);
let {inputProps} = useRadio(props state);
let {isFocusVisible focusProps} = useFocusRing();
let isChecked = inputPropschecked;
let strokeWidth = isChecked ? 6 : 2;
return (
<label style={display: 'flex' alignItems: 'center'}>
<VisuallyHidden>
<input ...inputProps ...focusProps />
</VisuallyHidden>
<svg
width=24
height=24
aria-hidden="true"
style={marginRight: 4}>
<circle
cx=12
cy=12
r=8 - strokeWidth / 2
fill="none"
stroke= isChecked ? 'orange' : 'gray'
strokeWidth= strokeWidth
/>
isFocusVisible && (
<circle
cx=12
cy=12
r=11
fill="none"
stroke="orange"
strokeWidth=2
/>
)
</svg>
children
</label>
);
}
<RadioGroup label="Favorite pet">
<Radio value="dogs">Dogs</Radio>
<Radio value="cats">Cats</Radio>
</RadioGroup>
import {VisuallyHidden} from '@react-aria/visually-hidden';
import {useFocusRing} from '@react-aria/focus';
// RadioGroup is the same as in the previous example
let RadioContext = ReactcreateContext();
function RadioGroup(
props
) {
let {
children
label
} = props;
let state = useRadioGroupState(
props
);
let {
radioGroupProps
labelProps
} = useRadioGroup(
props
state
);
return (
<div
...radioGroupProps>
<span
...labelProps>
label
</span>
<RadioContextProvider
value= state>
children
</RadioContextProvider>
</div>
);
}
function Radio(props) {
let {children} = props;
let state = ReactuseContext(
RadioContext
);
let {
inputProps
} = useRadio(
props
state
);
let {
isFocusVisible
focusProps
} = useFocusRing();
let isChecked =
inputPropschecked;
let strokeWidth = isChecked
? 6
: 2;
return (
<label
style={
display: 'flex'
alignItems:
'center'
}>
<VisuallyHidden>
<input
...inputProps
...focusProps
/>
</VisuallyHidden>
<svg
width=24
height=24
aria-hidden="true"
style={
marginRight: 4
}>
<circle
cx=12
cy=12
r=
8 -
strokeWidth /
2
fill="none"
stroke=
isChecked
? 'orange'
: 'gray'
strokeWidth=
strokeWidth
/>
isFocusVisible && (
<circle
cx=12
cy=12
r=11
fill="none"
stroke="orange"
strokeWidth=
2
/>
)
</svg>
children
</label>
);
}
<RadioGroup label="Favorite pet">
<Radio value="dogs">
Dogs
</Radio>
<Radio value="cats">
Cats
</Radio>
</RadioGroup>
Internationalization#
RTL#
In right-to-left languages, the radio group and radio buttons should be mirrored. The group should be right-aligned, and the radio should be placed on the right side of the label. Ensure that your CSS accounts for this.
Name | Type | Default | Description |
orientation | Orientation | 'vertical' | The axis the Radio Button(s) should align with. |
name | string | — | The unique identifying name of the RadioGroup that is applied to the Radios within the group. See MDN. |
value | string | — | The current value (controlled). |
defaultValue | string | — | The default value (uncontrolled). |
onChange | (
(value: string
)) => void | — | Handler that is called when the value changes. |
isDisabled | boolean | — | Whether the input is disabled. |
isReadOnly | boolean | — | Whether the input can be selected but not changed by the user. |
validationState | ValidationState | — | Whether the input should display its "valid" or "invalid" visual styling. |
isRequired | boolean | — | Whether user input is required on the input before form submission.
Often paired with the necessityIndicator prop to add a visual indicator to the input. |
label | ReactNode | — | The content to display as the label. |
id | string | — | The element's unique identifier. |
aria-label | string | — | Defines a string value that labels the current element. |
aria-labelledby | string | — | Identifies the element (or elements) that labels the current element. |
aria-describedby | string | — | Identifies the element (or elements) that describes the object. |
aria-details | string | — | Identifies the element (or elements) that provide a detailed, extended description for the object. |
aria-errormessage | string | — | Identifies the element that provides an error message for the object. |
Properties
Name | Type | Description |
name | string | The name for the group, used for native form submission. |
selectedValue | string | null | The currently selected value. |
lastFocusedValue | string | null | The value of the last focused radio. |
Methods
Method | Description |
setSelectedValue(
(value: string
)): void | Sets the selected value. |
setLastFocusedValue(
(value: string
)): void | Sets the last focused value. |
Name | Type | Description |
radioGroupProps | HTMLAttributes<HTMLElement> | Props for the radio group wrapper element. |
labelProps | HTMLAttributes<HTMLElement> | Props for the radio group's visible label (if any). |
Name | Type | Description |
value | string | The value of the radio button, used to identify it in a RadioGroup. See MDN. |
isRequired | boolean | Whether the Radio control is required. See MDN. |
isReadOnly | boolean | Whether the Radio can be interacted with but cannot have its selection state changed. |
children | ReactNode | The label for the Radio. Accepts any renderable node. |
isDisabled | boolean | Whether the radio button is disabled or not. Shows that a selection exists, but is not available in that circumstance. |
autoFocus | boolean | Whether the element should receive focus on render |
onFocus | (
(e: FocusEvent
)) => void | Handler that is called when the element receives focus. |
onBlur | (
(e: FocusEvent
)) => void | Handler that is called when the element loses focus. |
onFocusChange | (
(isFocused: boolean
)) => void | Handler that is called when the element's focus status changes. |
onKeyDown | (
(e: KeyboardEvent
)) => void | Handler that is called when a key is pressed. |
onKeyUp | (
(e: KeyboardEvent
)) => void | Handler that is called when a key is released. |
id | string | The element's unique identifier. |
aria-label | string | Defines a string value that labels the current element. |
aria-labelledby | string | Identifies the element (or elements) that labels the current element. |
aria-describedby | string | Identifies the element (or elements) that describes the object. |
aria-details | string | Identifies the element (or elements) that provide a detailed, extended description for the object. |
Name | Type | Description |
inputProps | InputHTMLAttributes<HTMLElement> | Props for the input element |
Provides state management for a radio group component. Provides a name for the group, and manages selection and focus state.
useRadioGroupState(
(props: RadioGroupProps
)): RadioGroupState
Name | Type | Default | Description |
orientation | Orientation | 'vertical' | The axis the Radio Button(s) should align with. |
name | string | — | The unique identifying name of the RadioGroup that is applied to the Radios within the group. See MDN. |
value | string | — | The current value (controlled). |
defaultValue | string | — | The default value (uncontrolled). |
onChange | (
(value: string
)) => void | — | Handler that is called when the value changes. |
isDisabled | boolean | — | Whether the input is disabled. |
isReadOnly | boolean | — | Whether the input can be selected but not changed by the user. |
validationState | ValidationState | — | Whether the input should display its "valid" or "invalid" visual styling. |
isRequired | boolean | — | Whether user input is required on the input before form submission.
Often paired with the necessityIndicator prop to add a visual indicator to the input. |
label | ReactNode | — | The content to display as the label. |
Determines whether a focus ring should be shown to indicate keyboard focus. Focus rings are visible only when the user is interacting with a keyboard, not with a mouse, touch, or other input methods.
useFocusRing(
(props: FocusRingProps
)): FocusRingAria
Name | Type | Default | Description |
within | boolean | 'false' | Whether to show the focus ring when something inside the container element has focus (true), or only if the container itself has focus (false). |
isTextInput | boolean | — | Whether the element is a text input. |
autoFocus | boolean | — | Whether the element will be auto focused. |
Name | Type | Description |
isFocused | boolean | Whether the element is currently focused. |
isFocusVisible | boolean | Whether keyboard focus should be visible. |
focusProps | HTMLAttributes<HTMLElement> | Props to apply to the container element with the focus ring. |