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
yarn add @palmerhq/radio-group
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-button';
import '@palmerhq/radio-button/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>
</>
);
}import * as React from 'react';
import { Formik, Form, useField } from 'formik';
import { RadioGroup, Radio } from '@palmerhq/radio-button';
import '@palmerhq/radio-button/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>
);
}This renders a div and will pass through all props to the DOM element. It's children must be <Radio> components.
This should match the id you used to label the radio group.
<h3 id="color">Color</h3>
<RadioGroup labelledBy="color">
{/* ... */}
</RadioGroup>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-button';
import '@palmerhq/radio-button/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>
</>
);
}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>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.
This renders a div and will pass through all props to the DOM element.
Required
The value of the radio button. This will be set / passed back to the <RadioGroup onChange> when the item is selected.
Callback function for when the item is focused
Callback function for when the item is blurred
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%);
}- 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.
| Key | Function |
|---|---|
| Tab |
|
| Space |
|
| Right arrow |
|
| Down arrow |
|
| Left arrow |
|
| Up arrow |
|
| 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 |
|
- Jared Palmer (@jaredpalmer)
MIT License
