@palmerhq/radio-group
An accessible WAI-ARIA 1.1-compliant Radio Group React component.
- Installation
- Usage
- API Reference
- Accessibility Features
- Keyboard Support
- Role, Property, State, and Tabindex Attributes
- Authors
Installation
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.
Usage
import * as React from 'react'; import { RadioGroup, Radio } from '@palmerhq/radio-group'; import '@palmerhq/radio-group/styles.css'; // use the default styles function App() { const [value, setValue] = React.useState<string | undefined>(); return ( <> <h3 id="color">Color</h3> <RadioGroup labelledBy="color" value={value} onChange={value => setValue(value)} > <Radio value="blue">Blue</Radio> <Radio value="red">Red</Radio> <Radio value="green">Green</Radio> </RadioGroup> </> ); }
Usage with Formik v2
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 styles function FRadioGroup(props) { const [{ onChange, onBlur, ...field }] = useField(props.name); return ( <RadioGroup {...props} {...field} labelledBy={props.name} onBlur={onBlur(props.name)} onChange={onChange(props.name)} /> ); } function App() { return ( <Formik initialValues={{ color: '' }} validationSchema={Yup.object().shape({ color: Yup.string().required(), })} onSubmit={(values, { setSubmitting }) => { setTimeout(() => { alert(JSON.stringify(values, null, 2)); setSubmitting(false); }, 500); }} > <Form> <h3 id="color">Color</h3> <FRadioGroup name="color"> <Radio value="blue">Blue</Radio> <Radio value="red">Red</Radio> <Radio value="green">Green</Radio> </FRadioGroup> </Form> </Formik> ); }
API Reference
<RadioGroup />
This renders a div
and will pass through all props to the DOM element. It's children must be <Radio>
components.
labelledBy?: string
This should match the id
you used to label the radio group.
<h3 id="color">Color</h3> <RadioGroup labelledBy="color"> {/* ... */} </RadioGroup>
onChange: (value: any) => void
A callback function that will be fired with the value
of 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 styles function App() { const [value, setValue] = React.useState<string | undefined>(); return ( <> <h3 id="color">Color</h3> <RadioGroup labelledBy="color" value={value} onChange={value => setValue(value)} > <Radio value="blue">Blue</Radio> <Radio value="red">Red</Radio> <Radio value="green">Green</Radio> </RadioGroup> </> ); }
children: React.ComponentType<RadioProps>[]
Required
The children of a <RadioGroup>
can ONLY be <Radio>
components. In order to support compliant keyboard behavior, each sibling must know the value of the whole group and so React.Children.map
is used internally.
<h3 id="color">Color</h3> <RadioGroup labelledBy="color"> {/* ... */} </RadioGroup>
value: any
Required
The current value of the radio group. This is shallowly compared to each value
prop of the child <Radio>
components to determine which item is active.
<Radio>
This renders a div
with a data attribute data-palmerhq-radio
and all the relevant perfect aria attributes. The React component will pass through all props to the DOM element.
value: any
Required
The value of the radio button. This will be set / passed back to the <RadioGroup onChange>
when the item is selected.
onFocus?: () => void
Callback function for when the item is focused. When focused, a data attribute data-palmerhq-radio-focus
is 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?: () => void
Callback function for when the item is blurred
Underlying DOM Structure
For reference, the underlying HTML DOM structure are all div
s and looks as follows.
<div role="radiogroup" aria-labelledby="color" data-palmerhq-radio-group="true"> <div role="radio" tabindex="0" aria-checked="false" data-palmerhq-radio="true" data-palmerhq-radio-focus="false" > Red </div> <div role="radio" tabindex="-1" aria-checked="false" data-palmerhq-radio="true" data-palmerhq-radio-focus="false" > Green </div> <div role="radio" tabindex="-1" aria-checked="false" data-palmerhq-radio="true" data-palmerhq-radio-focus="false" > Blue </div> </div>
Overriding 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]::before, [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-checked
state with the visual state indicator. - Uses CSS
:hover
and:focus
pseudo-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 |
|
Space |
|
Right arrow |
|
Down arrow |
|
Left arrow |
|
Up arrow |
|
Role, Property, State, and Tabindex Attributes
Role | Attributes | Element | Usage |
---|---|---|---|
radiogroup | div |
| |
aria-labelledby="[IDREF]" | div | Refers to the element that contains the label of the radio group. | |
radio | div |
| |
tabindex="-1" | div |
| |
tabindex="0" | div |
| |
aria-checked="false" | div |
| |
aria-checked="true" | div |
|
Authors
- Jared Palmer (@jaredpalmer)
MIT License