react-roving-tabindex
React Hooks implementation of a roving tabindex. See the storybook here to try it out.
![CircleCI](https://img.shields.io/circleci/project/github/stevejay/react-roving-tabindex/master.svg)
Background
The roving tabindex is an accessibility pattern for a grouped set of inputs. It assists people who are using their keyboard to navigate your Web site. All inputs in a group get treated as a single tab stop, which speeds up keyboard navigation. The last focused input in a group is also remembered, so that it can receive focus again when the user tabs back to the group.
When in the group, the left and right (or up and down) arrow keys move between the inputs. The Home and End keys (Fn+LeftArrow and Fn+RightArrow on macOS) move to the group's first and last inputs respectively.
More information about the roving tabindex pattern is available here and here.
Implementation Considerations
There are two main architectural choices to be made:
- Whether dynamic enabling and unenabling of the inputs in the group should be supported.
- How the inputs in a group are identified, including if they need to be direct children of the group container.
This package opts to support dynamic enabling and unenabling. It also allows inputs to be nested as necessary within subcomponents and wrapper elements. It uses React Context to communicate between the managing group component and the nested inputs.
This package does not support nesting one roving tabindex group inside another. I believe that this complicates keyboard navigation too much.
Requirements
This package has been written using the React Hooks API, so it is only usable with React version 16.8 onwards.
Installation
npm install --save react-roving-tabindex
This package includes TypeScript typings.
If you need to support IE 11 then you need to also install a polyfill for Array.prototype.findIndex
. One option is the polyfill on the MDN website.
Usage
There is a storybook for this package here.
import React from "react";
import {
RovingTabIndexProvider,
useRovingTabIndex,
useFocusEffect
} from "react-roving-tabindex";
type Props = {
disabled?: boolean;
children: React.ReactNode;
};
const ToolbarButton = ({ disabled = false, children }: Props) => {
const ref = React.useRef<HTMLButtonElement>(null);
const [tabIndex, focused, handleKeyDown, handleClick] = useRovingTabIndex(
ref,
disabled
);
useFocusEffect(focused, ref);
return (
<button
ref={ref}
tabIndex={tabIndex} // tabIndex must be applied here.
disabled={disabled}
onKeyDown={handleKeyDown}
onClick={handleClick}
>
{children}
</button>
);
};
const App = () => (
<RovingTabIndexProvider>
{/*
it's fine for the roving tabindex components to be nested
in other DOM elements or React components.
*/}
<ToolbarButton>First Button</ToolbarButton>
<ToolbarButton>Second Button</ToolbarButton>
</RovingTabIndexProvider>
);
Custom ID
You can optionally pass a custom ID to the useRovingTabIndex
hook as the third argument:
const [tabIndex, focused, handleKeyDown, handleClick] = useRovingTabIndex(
ref,
disabled,
"custom-id-1"
);
This is useful if you need to support server-side rendering. The value initially passed will be used for the lifetime of the containing component. You cannot dynamically change this ID.
Navigation
By default the left and right arrow keys are used to move between inputs. You can change this using the direction
prop on the provider:
<RovingTabIndexProvider direction="horizontal|vertical|both" />
The vertical
option requires the up and down arrows for navigation. The both
option is a mix of the other two options.
Upgrading
From version 1
This package no longer includes a ponyfill for Array.prototype.findIndex
. If you need to support IE 11 then you now need to install a polyfill for that method. One option is the polyfill on the MDN website.
License
MIT © stevejay
Development
If you have build errors when building Storybook locally, you are likely using Node v13. Please use either Node v14+ or Node v12.
Issues
- The
@types/styled-components
package is currently downgraded to v4.1.8 because of this issue. This only affects the Storybook build.