@palmerhq/radio-group
v1.0.2
Published
An accessible [WAI-ARIA 1.1-compliant Radio Group](https://www.w3.org/TR/wai-aria-practices-1.1/#radiobutton) React component.
Downloads
120
Readme
@palmerhq/radio-group
An accessible WAI-ARIA 1.1-compliant Radio Group React component.
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.
as?: React.ComponentType
Component to use a the wrapper. Default is <div>
.
autoFocus?: boolean
Whether to autoFocus the selected radio option.
<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
as?: React.ComponentType
Component to use as radio. Default is <div>
.
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.
Authors
- Jared Palmer (@jaredpalmer)
MIT License