1.0.2 • Published 4 years ago

@palmerhq/radio-group v1.0.2

Weekly downloads
92
License
MIT
Repository
-
Last release
4 years ago

@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 divs 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


MIT License

1.0.2

4 years ago

1.0.1

4 years ago

1.0.0

4 years ago

0.2.0

5 years ago

0.1.2

5 years ago

0.1.1

5 years ago

0.1.0

5 years ago