Search code examples
htmlreactjsaccessibilityhtml-listsarrow-keys

React: accessible unordered list of buttons


I'm creating an unordered list of buttons and would like to make it accessible. (Note: I found this SO post, but it doesn't answer my question.)

My code (simplified, in React) looks like this:

import React from "react";
import "./styles.css";

const items = [1,2,3,4,5];

const renderListItem = (_, index) => (
  <li key={index}>
    <button>Button {index}</button>
  </li>
);

export default function App() {
  return (
    <main className="App">
      <button>Foo</button>
      <button>Bar</button>

      <ul tabIndex="0">
        {items.map(renderListItem)}
      </ul>

      <button>Baz</button>
    </main>
  );
}

I was able to make the ul tabbable by giving it a tabIndex of 0.

My problem is that this the user can still tab through each list item. In my real application the list consists of dozens of buttons and I feel like it would be a bad UX for people that rely on a11y having to tab through all of them. I've read that best practice would be to make the ul tabbable (as I did) and then allow them to enter the list using "space" and navigate the list using arrow keys, so that they can tab out at any point to the next element.

How could I something like this in React? I'm looking to learn how to:

  1. Enter the list using "space".
  2. Make the buttons "untabbable" and instead ...
  3. Navigate the focus between the buttons using arrow keys.

Solution

  • If you have several cases of such long groups, you should probably provide other means to navigate as well. Headlines, for example, which would allow screen reader users to skip such groups or target a specific one.

    Concerning tab navigation: I stumbled across a pattern called a roving tabindex, which is explained well on MDN's Keyboard-navigable JavaScript widgets page

    The technical implementation is as following:

    • Do NOT make the container focusable
    • Make only the first item in the list (menu) focusable by giving all others to tabindex="-1"
    • Add React logic to move (implicit) tabindex="0" attribute between items by means of arrow keys

    There seems to be the npm package react-roving-tabindex that already implements that logic, even with grid-navigation.

    Otherwise a quick-fix might be to provide a hidden skip link just before the list of buttons that allows jumping over them. I'm assuming you are providing a title for your list?

    <hX>Title explaining the group</hX>
    <p><a href="#skip-buttons">Skip group</a></p>
    <ul>
    <li><button>Button 1</button></li>
    …
    </ul>
    
    <button id="skip-buttons">Baz</button>