All Projects → palmerhq → Radio Group

palmerhq / Radio Group

Licence: mit
845 byte WAI-ARIA 1.1 compliant radio group React component

Programming Languages

typescript
32286 projects

Projects that are alternatives of or similar to Radio Group

accessible-forms
A series of HTML test cases to determine how specific elements are announced in different screen reader / browser combinations.
Stars: ✭ 159 (+19.55%)
Mutual labels:  forms, a11y
Chakra Ui
⚡️ Simple, Modular & Accessible UI Components for your React Applications
Stars: ✭ 22,745 (+17001.5%)
Mutual labels:  a11y, wai-aria
sublime-wai-aria
WAI-ARIA Roles, States and Properties auto-completion for Sublime Text
Stars: ✭ 21 (-84.21%)
Mutual labels:  a11y, wai-aria
Bootstrap Vue
BootstrapVue provides one of the most comprehensive implementations of Bootstrap v4 for Vue.js. With extensive and automated WAI-ARIA accessibility markup.
Stars: ✭ 13,603 (+10127.82%)
Mutual labels:  a11y, wai-aria
Form Js
Easily create web forms. Supports Meteor, AngularJS, React, Polymer and any CSS library, e.g. Bootstrap.
Stars: ✭ 9 (-93.23%)
Mutual labels:  forms, radio-buttons
ari
Accessible unstyled vue components
Stars: ✭ 22 (-83.46%)
Mutual labels:  a11y, wai-aria
Js Offcanvas
A lightweight, flexible jQuery off-canvas navigation plugin which lets you create fully accessible sidebar or top/bottom sliding (or push) panels with keyboard interactions and ARIA attributes.
Stars: ✭ 272 (+104.51%)
Mutual labels:  a11y, wai-aria
accessibility-testing-tools
A collection of useful tools for accessibility testing and debugging in the browser, online and desktop
Stars: ✭ 18 (-86.47%)
Mutual labels:  a11y, wai-aria
Reakit
Toolkit for building accessible rich web apps with React
Stars: ✭ 5,265 (+3858.65%)
Mutual labels:  a11y, wai-aria
A11y styled form controls
Various styled accessible form controls
Stars: ✭ 335 (+151.88%)
Mutual labels:  a11y, forms
Visible
🦉 Accessibility testing framework at the next level
Stars: ✭ 164 (+23.31%)
Mutual labels:  a11y, wai-aria
Awesome A11y
A curate list about A11Y ♿️
Stars: ✭ 1,210 (+809.77%)
Mutual labels:  a11y, wai-aria
React Menu
React component for building accessible menu, dropdown, submenu, context menu and more.
Stars: ✭ 237 (+78.2%)
Mutual labels:  radio-buttons, wai-aria
LC-switch
Superlight vanilla javascript plugin improving forms look and functionality
Stars: ✭ 31 (-76.69%)
Mutual labels:  forms, radio-buttons
Chakra Ui
⚡️Simple, Modular & Accessible UI Components for your React Applications
Stars: ✭ 295 (+121.8%)
Mutual labels:  a11y, wai-aria
Chakra Ui Vue
⚡️ Build scalable and accessible Vue.js applications with ease.
Stars: ✭ 993 (+646.62%)
Mutual labels:  a11y, wai-aria
Nostyle
Design System
Stars: ✭ 101 (-24.06%)
Mutual labels:  a11y, forms
Snodge
Randomly mutate JSON, XML, HTML forms, text and binary data for fuzz testing
Stars: ✭ 121 (-9.02%)
Mutual labels:  forms
A11y Dialog
A very lightweight and flexible accessible modal dialog script.
Stars: ✭ 1,768 (+1229.32%)
Mutual labels:  a11y
React Controlled Form
Flexible, Modular & Controlled Forms for React and Redux
Stars: ✭ 121 (-9.02%)
Mutual labels:  forms

@palmerhq/radio-group

An accessible WAI-ARIA 1.1-compliant Radio Group React component.

Radio group demo

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.

Keyboard Support

Key Function
Tab
  • Moves focus to the checked radio button in the radiogroup.
  • If a radio button is not checked, focus moves to the first radio button in the group.
Space
  • If the radio button with focus is not checked, changes the state to checked.
  • 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 first radio 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 first radio 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 last radio 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 last radio 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 of radio 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 ARIA radio 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 the aria-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 the aria-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


MIT License

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].