845 byte WAI-ARIA 1.1 compliant radio group React component
@palmerhq/radio-group
An accessible WAI-ARIA 1.1-compliant Radio Group React component.
@palmerhq/radio-group
yarn add @palmerhq/radio-group
Or try it out in your browser on CodeSandbox
Note: This package uses
Array.prototype.findIndex, so be sure that you have properly polyfilled.
import * as React from 'react'; import { RadioGroup, Radio } from '@palmerhq/radio-group'; import '@palmerhq/radio-group/styles.css'; // use the default stylesfunction App() { const [value, setValue] = React.useState();
return ( <>
Color
setValue(value)} > Blue Red Green > ); }
import * as React from 'react'; import { Formik, Form, useField } from 'formik'; import { RadioGroup, Radio } from '@palmerhq/radio-group'; import '@palmerhq/radio-group/styles.css'; // use the default stylesfunction FRadioGroup(props) { const [{ onChange, onBlur, ...field }] = useField(props.name); return ( ); }
function App() { return ( { setTimeout(() => { alert(JSON.stringify(values, null, 2)); setSubmitting(false); }, 500); }} >
Color
Blue Red Green ); }
This renders a
divand will pass through all props to the DOM element. It's children must be components.
labelledBy?: string
This should match the
idyou used to label the radio group.
Color
{/* ... */}
onChange: (value: any) => void
A callback function that will be fired with the
valueof the newly selected item.
import * as React from 'react'; import { RadioGroup, Radio } from '@palmerhq/radio-group'; import '@palmerhq/radio-group/styles.css'; // use the default stylesfunction App() { const [value, setValue] = React.useState();
return ( <>
Color
setValue(value)} > Blue Red Green > ); }
children: React.ComponentType[]
Required
The children of a
can ONLY be components. In order to support compliant keyboard behavior, each sibling must know the value of the whole group and soReact.Children.mapis used internally.
Color
{/* ... */}
value: any
Required
The current value of the radio group. This is shallowly compared to each
valueprop of the child components to determine which item is active.
as?: React.ComponentType
Component to use a the wrapper. Default is
.
autoFocus?: booleanWhether to autoFocus the selected radio option.
This renders a
divwith a data attributedata-palmerhq-radioand all the relevant perfect aria attributes. The React component will pass through all props to the DOM element.
value: anyRequired
The value of the radio button. This will be set / passed back to the
when the item is selected.
onFocus?: () => voidCallback function for when the item is focused. When focused, a data attribute
data-palmerhq-radio-focusis set to"true". You can thus apply the selector to manage focus style like so:[data-palmerhq-radio][data-palmerhq-radio-focus='true'] { background: blue; }
onBlur?: () => voidCallback function for when the item is blurred
as?: React.ComponentTypeComponent to use as radio. Default is
.Underlying DOM Structure
For reference, the underlying HTML DOM structure are all
divs and looks as follows.RedGreenBlueOverriding Styles
These are the default styles. Copy and paste the following into your app to customize them.
[data-palmerhq-radio-group] { padding: 0; margin: 0; list-style: none; }[data-palmerhq-radio-group]:focus { outline: none; }
data-palmerhq-radio { border: 2px solid transparent; border-radius: 5px; display: inline-block; position: relative; padding: 0.125em; padding-left: 1.5em; padding-right: 0.5em; cursor: default; outline: none; }
data-palmerhq-radio + data-palmerhq-radio { margin-left: 1em; }
data-palmerhq-radio::after { position: absolute; top: 50%; left: 7px; transform: translate(-20%, -50%); content: ''; }
data-palmerhq-radio::before { width: 14px; height: 14px; border: 1px solid hsl(0, 0%, 66%); border-radius: 100%; background-image: linear-gradient(to bottom, hsl(300, 3%, 93%), #fff 60%); }
data-palmerhq-radio:active::before { background-image: linear-gradient( to bottom, hsl(300, 3%, 73%), hsl(300, 3%, 93%) ); }
[data-palmerhq-radio][aria-checked='true']::before { border-color: hsl(216, 80%, 50%); background: hsl(217, 95%, 68%); background-image: linear-gradient( to bottom, hsl(217, 95%, 68%), hsl(216, 80%, 57%) ); }
[data-palmerhq-radio][aria-checked='true']::after { display: block; border: 0.1875em solid #fff; border-radius: 100%; transform: translate(25%, -50%); }
[data-palmerhq-radio][aria-checked='mixed']:active::before, [data-palmerhq-radio][aria-checked='true']:active::before { background-image: linear-gradient( to bottom, hsl(216, 80%, 57%), hsl(217, 95%, 68%) 60% ); }
data-palmerhq-radio:hover::before { border-color: hsl(216, 94%, 65%); }
[data-palmerhq-radio][data-palmerhq-radio-focus='true'] { border-color: hsl(216, 94%, 73%); background-color: hsl(216, 80%, 97%); }
data-palmerhq-radio:hover { background-color: hsl(216, 80%, 92%); }
Accessibility Features
- Uses CSS attribute selectors for synchronizing
aria-checkedstate with the visual state indicator.- Uses CSS
:hoverand:focuspseudo-selectors for styling visual keyboard focus and hover.- Focus indicator encompasses both radio button and label, making it easier to perceive which option is being chosen.
- Hover changes background of both radio button and label, making it easier to perceive that clicking either the label or button will activate the radio button.
Keyboard Support
Key Function Tab
- Moves focus to the checked
radio
button in theradiogroup
.- If a
radio
button is not checked, focus moves to the firstradio
button in the group.Space
- If the
radio
button with focus is not checked, changes the state tochecked
.- Otherwise, does nothing.
- Note: The state where a radio is not checked only occurs on page load.
Right arrow
- Moves focus to and checks the next
radio
button in the group.- If focus is on the last
radio
button, moves focus to the firstradio
button.- The state of the previously checked radio button is changed to unchecked.
Down arrow
- Moves focus to and checks the next
radio
button in the group.- If focus is on the last
radio
button, moves focus to the firstradio
button.- The state of the previously checked radio button is changed to unchecked.
Left arrow
- Moves focus to and checks the previous
radio
button in the group.- If focus is on the first
radio
button, moves focus to and checks the lastradio
button.- The state of the previously checked radio button is changed to unchecked.
Up arrow
- Moves focus to and checks the previous
radio
button in the group.- If focus is on the first
radio
button, moves focus to and checks the lastradio
button.- The state of the previously checked radio button is changed to unchecked.
Role, Property, State, and Tabindex Attributes
Role Attributes Element Usage radiogroup
div
- Identifies the
div
element as a container for a group ofradio
buttons.- Is not focusable because focus is managed using a roving tabindex strategy as described below.
aria-labelledby="[IDREF]"
div
Refers to the element that contains the label of the radio group. radio
div
- Identifies the
div
element as an ARIAradio
button.- The accessible name is computed from the child text content of the
div
element.tabindex="-1"
div
- Makes the element focusable but not part of the page Tab sequence.
- Applied to all radio buttons contained in the radio group except for one that is included in the page Tab sequence.
- This approach to managing focus is described in the section on roving tabindex.
tabindex="0"
div
- Makes the radio button focusable and includes it in the page Tab sequence.
- Set on only one radio in the radio group.
- On page load, is set on the first radio button in the radio group.
- Moves with focus inside the radio group so the most recently focused radio button is included in the page Tab sequence.
- This approach to managing focus is described in the section on roving tabindex.
aria-checked="false"
div
- Identifies
radio
buttons which are not checked.- CSS attribute selectors (e.g.
[aria-checked="false"]
) are used to synchronize the visual states with the value of thearia-checked
attribute.- The CSS
::before
pseudo-class is used to indicate visual state of unchecked radio buttons to support high contrast settings in operating systems and browsers.aria-checked="true"
div
- Identifies the
radio
button which is checked.- CSS attribute selectors (e.g.
[aria-checked="true"]
) are used to synchronize the visual states with the value of thearia-checked
attribute.- The CSS
::before
pseudo-class is used to indicate visual state of checked radio buttons to support high contrast settings in operating systems and browsers.Authors
- Jared Palmer (@jaredpalmer)
MIT License