Security News
Fluent Assertions Faces Backlash After Abandoning Open Source Licensing
Fluent Assertions is facing backlash after dropping the Apache license for a commercial model, leaving users blindsided and questioning contributor rights.
react-roving-tabindex
Advanced tools
React implementation of a roving tabindex, now with grid support
React Hooks implementation of a roving tabindex, now with grid support. See the storybook here to try it out.
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 the group is also remembered. It receives focus again when the user tabs back into the group.
When in the group, the ArrowLeft and ArrowRight keys move focus between the inputs. (You can also configure this library so that the ArrowUp and ArrowDown keys move focus.) The Home and End keys (Fn+LeftArrow and Fn+RightArrow on macOS) move focus to the group's first and last inputs respectively.
More information about the roving tabindex pattern is available here and here.
This pattern can also be used for a grid of items, as in this calendar example. Conventionally, the containing element for the grid should be given the grid
role, and each grid item should be given the gridcell
role. The ArrowLeft and ArrowRight keys are used to move focus between inputs in a row, while the ArrowUp and ArrowDown keys move focus between the rows. The Home and End keys (Fn+LeftArrow and Fn+RightArrow on macOS) move focus to a row's first and last inputs respectively. If the Control key is held while pressing the Home and End keys then focus is moved to the very first and very last input in the grid respectively.
There are two main architectural choices to be made:
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.
This package is designed as a general solution for a roving tabindex in a toolbar or a smallish grid. If you need a roving tabindex in a very large grid or table (a few hundred cells or more) then you will likely be better served with a bespoke implementation. By not including any unnecessary flexibility that this package offers then you should create a more performant implementation. For example, you might not need to support the enabling and unenabling of tab stops. It also takes time to register the cells with the package, and there is an overhead to creating the row index mapping for a grid.
This package has been written using the React Hooks API, so it is only usable with React version 16.8 onwards. It has peer dependencies of react
and react-dom
.
npm install react-roving-tabindex
# or
yarn add react-roving-tabindex
This package includes TypeScript typings.
If you need to support IE then you also need to install polyfills for Array.prototype.findIndex
and Map
. If you are using React with IE then you already need to use a Map
polyfill. If you use a global polyfill like core-js or babel-polyfill then it should include a findIndex
polyfill.
There is a storybook for this package here, with both toolbar and grid usage examples.
import React, { ReactNode, useRef } from "react";
import {
RovingTabIndexProvider,
useRovingTabIndex,
useFocusEffect
} from "react-roving-tabindex";
type Props = {
disabled?: boolean;
children: ReactNode;
};
const ToolbarButton = ({ disabled = false, children }: Props) => {
// The ref of the input to be controlled.
const ref = useRef<HTMLButtonElement>(null);
// handleKeyDown and handleClick are stable for the lifetime of the component:
const [tabIndex, focused, handleKeyDown, handleClick] = useRovingTabIndex(
ref, // Don't change the value of this ref.
disabled // But change this as you like throughout the lifetime of the component.
);
// Use some mechanism to set focus on the button if it gets focus.
// In this case I use the included useFocusEffect hook:
useFocusEffect(focused, ref);
return (
<button
ref={ref}
tabIndex={tabIndex} // tabIndex must be applied here
disabled={disabled}
onKeyDown={handleKeyDown} // handler applied here
onClick={handleClick} // handler applied here
>
{children}
</button>
);
};
const SomeComponent = () => (
// Wrap each roving tabindex group in a RovingTabIndexProvider.
<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>
);
If you need to incorporate your own handling for onClick
and/or onKeyDown
events then just create handlers that invoke multiple handlers:
return (
<button
ref={ref}
tabIndex={tabIndex}
disabled={disabled}
onKeyDown={(event) => {
handleKeyDown(event); // handler from the hook
someKeyDownHandler(event); // your handler
}}
onClick={(event) => {
handleClick(event); // handler from the hook
someClickHandler(event); // your handler
}}
>
{children}
</button>
);
It is the ArrowLeft and ArrowRight keys that are used by default to move to the previous and next input respectively. The RovingTabIndexProvider
has an optional direction
property that allows you to change this:
const SomeComponent = () => (
<RovingTabIndexProvider direction="vertical">
{/* whatever */}
</RovingTabIndexProvider>
);
The default behaviour is selected by setting the direction to horizontal
. If the direction is set to vertical
then it is the ArrowUp and ArrowDown keys that are used to move to the previous and next input. If the direction is set to both
then both the ArrowLeft and ArrowUp keys can be used to move to the previous input, and both the ArrowRight and ArrowDown keys can be used to move to the next input. You can update this direction
value at any time.
You may want to set a particular element in the roving tabindex to be the initially tabbable element (i.e., the element that has a tabindex
of 0
). This could be because you remember the state of the UI and restore it when the user next uses the app.
To facilitate this, the RovingTabIndexProvider
has an optional initialTabElementSelector
prop. This takes a selector string that is used to identify the tab element that should be the initially tabbable element. Each tab element is tested using Element.matches()
. This requires that the selector identifies the tabbable element itself (rather than, say, a child element). Thus the selector is most likely to be an ID (e.g., '#bar'
) or a data selector (e.g., '[data-foo-id="bar"]'
). If used, the value of this prop should remain the same for the lifetime of the component.
To help with tracking the currently tabbable element in the roving tabindex, the RovingTabIndexProvider
offers an optional onTabElementSelected
callback prop. This callback is invoked whenever the user clicks on or uses the keyboard to select a tab element in the roving tabindex. The callback is invoked with that element. This allows you to get some information from that element, e.g., its ID or a data attribute. This information could be used later to provide the value for the initialTabElementSelector
prop. If used, the value of this prop should remain the same for the lifetime of the component. This might require that you use React.useCallback
to create a stable callback function.
This package supports a roving tabindex in a grid. For each usage of the useRovingTabIndex
hook in the grid, you must pass a row index value as a third argument to the hook:
const [tabIndex, focused, handleKeyDown, handleClick] = useRovingTabIndex(
ref,
disabled,
someRowIndexValue
);
The row index value must be the zero-based row index for the grid item that the hook is being used with. Thus all items that represent the first row of grid items should have 0
passed to the hook, the second row 1
, and so on. If the shape of the grid can change dynamically then it is fine to update the row index value. For example, the grid might initially have four items per row but get updated to have three items per row.
The direction
property of the RovingTabIndexProvider
is ignored when row indexes are provided. This is because the ArrowUp and ArrowDown keys are always used to move between rows.
There are a few breaking changes in version 2.
This package no longer includes a ponyfill for Array.prototype.findIndex
and now also uses the Map
class. If you need to support IE then you will need to install polyfills for both. That said, if you currently support IE then you are almost certainly using a suitable global polyfill already. Please see the Installation section earlier in this file for further guidance.
The optional ID argument that was the third argument to the useRovingTabIndex
hook has been replaced with the new optional row index argument. The ID argument was included to support server-side rendering (SSR) but it is not actually required. By default this library auto-generates an ID within the hook. This is not a problem in SSR because it never gets generated and serialized on the server. Thus it is fine for it to be auto-generated even when SSR needs to be supported. So if you have previously used the following...
const [...] = useRovingTabIndex(ref, true, id);
// ^^
... then you can simply remove that third argument:
const [...] = useRovingTabIndex(ref, true);
The version 1 release has no breaking changes compared to v0.9.0. The version bump was because the package had stabilized.
MIT © stevejay
If you have build errors when building the Storybook locally, you are likely using Node v13. Please use either Node v14+ or Node v12.
npm publish --tag next
.npm publish --tag latest
.FAQs
React implementation of a roving tabindex, now with grid support
We found that react-roving-tabindex demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Fluent Assertions is facing backlash after dropping the Apache license for a commercial model, leaving users blindsided and questioning contributor rights.
Research
Security News
Socket researchers uncover the risks of a malicious Python package targeting Discord developers.
Security News
The UK is proposing a bold ban on ransomware payments by public entities to disrupt cybercrime, protect critical services, and lead global cybersecurity efforts.