Combo box with ARIA 1.2 groups

A combo box modified to use ARIA 1.2 groups.

The group role is not allowed as a child of a listbox in ARIA 1.0 or ARIA 1.1 and consequently is not supported in the majority of screen readers.

In ARIA 1.2 the group role is now allowed.

In the main implementation groups are simulated with a visual text label, and hidden text for screen-readers. This examples modifies the combo-box to use true groups.

import { useState } from 'react';
import { ComboBox, useTokenSearch } from '@citizensadvice/react-combo-boxes';

const options = [
  { label: 'Apple' },
  { label: 'Orange', group: 'Citrus' },
  { label: 'Lemon', group: 'Citrus' },
  { label: 'Raspberry', group: 'Berry' },
  { label: 'Strawberry', group: 'Berry' },
];

function renderGroup(props, { groupChildren, group: { key, label } }) {
  return (
    <li
      key={key}
      role="group"
      aria-labelledby={key}
    >
      <div
        className="react-combo-boxes-combobox__group-label"
        id={key}
      >
        {label}
      </div>
      <ul
        role="presentation"
        className="react-combo-boxes-combobox__group"
      >
        {groupChildren}
      </ul>
    </li>
  );
}

export function Example() {
  const [value, setValue] = useState(null);
  const [search, setSearch] = useState(null);
  const [managedFocus, setManagedFocus] = useState(true);
  const filteredOptions = useTokenSearch(search, { options });

  return (
    <>
      <label
        id="select-label"
        htmlFor="select"
      >
        Select
      </label>
      <ComboBox
        id="select"
        aria-labelledby="select-label"
        value={value}
        onValue={setValue}
        onSearch={setSearch}
        options={filteredOptions}
        managedFocus={managedFocus}
        renderGroup={renderGroup}
        renderGroupAccessibleLabel={() => null}
      />

      <label>
        <input
          type="checkbox"
          onChange={({ target: { checked } }) => setManagedFocus(checked)}
          checked={managedFocus}
        />{' '}
        Toggle managed focus
      </label>

      <label htmlFor="output">Current value</label>
      <output
        htmlFor="select"
        id="output"
      >
        {JSON.stringify(value, undefined, ' ')}
      </output>
    </>
  );
}