/react-accessible-dropdown-menu-hook

A simple Hook for creating fully accessible dropdown menus in React

Primary LanguageTypeScriptMIT LicenseMIT

React Accessible Dropdown Menu Hook

This Hook handles all the accessibility logic when building a dropdown menu, dropdown button, etc., and leaves the design completely up to you. It also handles the logic for closing the menu when you click outside of it. View the demo.

Getting started

Install with Yarn or npm:

yarn add react-accessible-dropdown-menu-hook
npm install react-accessible-dropdown-menu-hook

Import the Hook:

import useDropdownMenu from 'react-accessible-dropdown-menu-hook';

Call the Hook, telling it how many items your menu will have.

const { buttonProps, itemProps, isOpen, setIsOpen } = useDropdownMenu(numberOfItems);

Spread the buttonProps onto a button:

<button {...buttonProps}>Example</button>

Create the menu with the role='menu' property and spread itemProps[x] onto each item:

<div className={isOpen ? 'visible' : ''} role='menu'>
    <a {...itemProps[0]} href='https://example.com'>Regular link</a>
    <a {...itemProps[1]} onClick={handleClick}>With click handler</a>
</div>

Done!

Design

This Hook returns an object of the following shape:

{
    buttonProps: {
        onKeyDown: (e: React.KeyboardEvent | React.MouseEvent) => void;
        onClick: (e: React.KeyboardEvent | React.MouseEvent) => void;
        tabIndex: 0;
        ref: React.RefObject<HTMLButtonElement>;
        role: 'button';
        'aria-haspopup': true;
        'aria-expanded': boolean;
    };
    itemProps: [
        {
            onKeyDown: (e: React.KeyboardEvent<HTMLAnchorElement>) => void;
            tabIndex: -1;
            role: 'menuitem';
            ref: React.RefObject<HTMLAnchorElement>;
        };
        ...
    ];
    isOpen: boolean;
    setIsOpen: (newValue: boolean) => void;
}
  • buttonProps: An object meant to be spread as properties on a <button /> element.
    • onKeyDown: A function which manages the behavior of your dropdown menu when a key is pressed while focused on the menu button.
    • onClick: The same function as onKeyDown(), but its behavior differs somewhat for click events.
    • tabIndex: Sets the tab index property of the <button /> element.
    • ref: A React ref applied to the <button /> element, used to manage focus.
    • role: A role property in accordance with WAI-ARIA guidelines.
    • aria-haspopup: An ARIA attribute indicating this button has a related menu element.
    • aria-expanded: An ARIA attribute indicating whether the menu is currently open.
  • itemProps: An array of objects meant to be spread as properties on <a /> elements that serve as menu items in your dropdown.
    • onKeyDown: A function which manages the behavior of your dropdown menu when a key is pressed while focused on a menu item.
    • tabIndex: Sets the tab index property to -1 to prevent the browser's native focusing logic. Focus is managed programatically by this Hook.
    • role: A role property in accordance with WAI-ARIA guidelines.
    • ref: A React ref applied to each menu item, used to manage focus.
  • isOpen: A boolean value indicating if the menu is open or closed. The developer should use this value to make the menu visible or not.
  • setIsOpen: A function useful for allowing the developer to programmatically open/close the menu.

Accessibility notes

Our team carefully studied and adhered to Web Content Accessibility Guidelines 2.1 and WAI-ARIA Authoring Practices 1.1 when designing this Hook. Here are some facets of accessibility that are handled automatically:

  • Careful following of the best practices for menus (WAI-ARIA: 3.15)
    • The only deviation is that the first menu item is only focused when the menu is revealed via the keyboard (see why)
  • Strict adherence to the best practices for menu buttons (WAI-ARIA: 3.16)
  • Full keyboard accessibility (WCAG: 2.1)
  • Use of ARIA properties and roles to establish relationships amongst elements (WCAG: 1.3.1)
  • Use of roles to identify the purpose of different parts of the menu (WCAG: 1.3.6)
  • Focusable components receive focus in an order that preserves meaning and operability (WCAG: 2.4.3)
  • Appears and operates in predictable ways (WCAG: 3.2)

For more details, see this comment.

Local development

To prep a just-cloned or just-cleaned repository for local development, run yarn dev.

To test the whole project, run yarn test.

To run the demo website locally, run cd ./demo && yarn start.

To format the code, run yarn format at either the project root or within the ./demo directory.

To clean the repository (removes any programmatically generated files), run yarn clean.