![Oracle Drags Its Feet in the JavaScript Trademark Dispute](https://cdn.sanity.io/images/cgdhsj6q/production/919c3b22c24f93884c548d60cbb338e819ff2435-1024x1024.webp?w=400&fit=max&auto=format)
Security News
Oracle Drags Its Feet in the JavaScript Trademark Dispute
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.
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, so that it can receive focus again when the user tabs back into the group.
When in the group, the ArrowLeft and ArrowRight (or ArrowUp and ArrowDown) keys move focus between the inputs. 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 on the MDN website. Conventionally, the containing element for the grid should be given the grid
role, and each grid item should given the gridcell
role. ArrowLeft and ArrowRight are used to move focus between items in a row, while the ArrowUp and ArrowDown keys move between the rows. The Home and End keys (Fn+LeftArrow and Fn+RightArrow on macOS) move focus to a row's first and last items respectively. If the Control key is held while pressing the Home and End keys then focus is moved respectively to the very first and very last item in the grid.
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.
If you need a roving tabindex in a very large grid/table then you might be better served with a bespoke implementation (but do test this first). By not including any unnecessary flexibility that this package offers then you will likely create a more performant implementation when the scale is such that performance becomes paramount. For example, you might not need to support the enabling and unenabling of tab stops. It also takes time to register the gridcells with the package, and there is an overhead to creating the row index mapping.
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 --save react-roving-tabindex
This package includes TypeScript typings.
If you need to support IE then you need to also 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 also include a findIndex
polyfill.
There is a storybook for this package here, with both basic and grid usage examples.
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) => {
// The ref of the input to be controlled.
const ref = React.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 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>
);
The useRovingTabIndex
hook has an optional third argument that is an options object. One use for it is to pass a custom ID:
const [tabIndex, focused, handleKeyDown, handleClick] = useRovingTabIndex(
ref,
disabled,
{ id: "custom-id-1" } // A custom ID.
);
This is required if you need to support server-side rendering (SSR). Note that the value initially passed will be used for the lifetime of the containing component; you cannot dynamically update this ID.
Note that it is fine to create a new object for this third argument each time the containing component is rendered; it will not trigger a re-render.
The default navigation configuration is the following:
Key | Resulting navigation |
---|---|
ArrowLeft | Previous tab stop |
ArrowRight | Next tab stop |
ArrowUp | Previous tab stop |
ArrowDown | Next tab stop |
Home | Very first tab stop |
Home+Ctrl | Very first tab stop |
End | Very last tab stop |
End+Ctrl | Very last tab stop |
The above configuration is included with this package as the following default key configuration object:
export const DEFAULT_KEY_CONFIG: KeyConfig = {
[Key.ARROW_LEFT]: Navigation.PREVIOUS,
[Key.ARROW_RIGHT]: Navigation.NEXT,
[Key.ARROW_UP]: Navigation.PREVIOUS,
[Key.ARROW_DOWN]: Navigation.NEXT,
[Key.HOME]: Navigation.VERY_FIRST,
[Key.HOME_WITH_CTRL]: Navigation.VERY_FIRST,
[Key.END]: Navigation.VERY_LAST,
[Key.END_WITH_CTRL]: Navigation.VERY_LAST
};
This object is used automatically by default. If this is not suitable for your use case then you can pass your own key configuration object to the RovingTabIndexProvider
. For example, you might want the ArrowUp and ArrowDown keys to have no effect:
import { DEFAULT_KEY_CONFIG, Key } from "react-roving-tabindex";
export const MY_CONFIG = {
...DEFAULT_KEY_CONFIG,
[Key.ARROW_UP]: undefined, // or null
[Key.ARROW_DOWN]: undefined // or null
};
const SomeComponent = () => (
<RovingTabIndexProvider keyConfig={MY_CONFIG}>
{/* Whatever here */}
</RovingTabIndexProvider>
);
Note that your custom key configuration object should be stable; please do not recreate it each time the component containing the RovingTabIndexProvider
is invoked as this will trigger a re-render. You can however pass a new configuration whenever you need to dynamically change the key configuration behaviour.
Note also that the TypeScript typings for the key configuration object somewhat constrain the possible navigation options for each key to the most logical choices given the established a11y conventions for a roving tabindex.
This package supports a roving tabindex in a grid of elements. For this to work you need to do two things in addition to following the instructions in the previous section.
Firstly you need to create and pass a custom key configuration object to the RovingTabIndexProvider
component. The exact configuration will depend on your needs but it will likely be something like the following:
// Create it...
const GRID_KEY_CONFIG: KeyConfig = {
[Key.ARROW_LEFT]: Navigation.PREVIOUS,
[Key.ARROW_RIGHT]: Navigation.NEXT,
[Key.ARROW_UP]: Navigation.PREVIOUS_ROW,
[Key.ARROW_DOWN]: Navigation.NEXT_ROW,
[Key.HOME]: Navigation.FIRST_IN_ROW,
[Key.HOME_WITH_CTRL]: Navigation.VERY_FIRST,
[Key.END]: Navigation.LAST_IN_ROW,
[Key.END_WITH_CTRL]: Navigation.VERY_LAST
};
// ... then use it:
const SomeComponent = () => (
<RovingTabIndexProvider keyConfig={GRID_KEY_CONFIG}>
{/* Whatever here */}
</RovingTabIndexProvider>
);
Secondly, for each usage of the useRovingTabIndex
hook you need to pass a third argument of a rowIndex
value in an options object:
const [tabIndex, focused, handleKeyDown, handleClick] = useRovingTabIndex(
ref,
disabled,
{ rowIndex: 0 } // 0, 1, 2, ...
);
The rowIndex
value should be the zero-based row index for the containing component in the grid it is in. Thus all items that represent the first row of grid items should have { rowIndex: 0 }
passed to the hook, the second row { rowIndex: 1 }
, and so on. If the shape of the grid can change dynamically then it is fine to update the rowIndex value. For example, the grid might initially has four items per row but be dynamically updated to three items per row.
Note that it is fine to create a new object for this third argument each time the containing component is rendered; it will not trigger an unnecessary re-render. Also, if required you can combine the rowIndex
with a custom id
in the same options object.
There are three breaking changes that might require updating your usages of this library.
Firstly, this package no longer includes a ponyfill for Array.prototype.findIndex
and also now uses the Map
class. If you need to support IE then you will need to install a polyfill for both, although if you already support IE then you are almost certainly using a suitable global polyfill. Please see the Installation section earlier in this file for further guidance.
Secondly, the direction
property of the RovingTabIndexProvider
has been removed. Instead the behaviour of the roving tabindex for the possible key presses (ArrowLeft, ArrowRight, ArrowUp, ArrowDown, Home, End, Home+Ctrl and End+Ctrl) is now configurable. Rather than specifying a direction, you can now pass a key configuration object to the RovingTabIndexProvider
component:
const SomeComponent = () => (
<RovingTabIndexProvider keyConfig={YOUR_CONFIG}>
{/* Whatever here */}
</RovingTabIndexProvider>
);
If you do not pass your own key configuration object then the following default one is automatically used:
export const DEFAULT_KEY_CONFIG: KeyConfig = {
[Key.ARROW_LEFT]: Navigation.PREVIOUS,
[Key.ARROW_RIGHT]: Navigation.NEXT,
[Key.ARROW_UP]: Navigation.PREVIOUS,
[Key.ARROW_DOWN]: Navigation.NEXT,
[Key.HOME]: Navigation.VERY_FIRST,
[Key.HOME_WITH_CTRL]: Navigation.VERY_FIRST,
[Key.END]: Navigation.VERY_LAST,
[Key.END_WITH_CTRL]: Navigation.VERY_LAST
};
This configuration specifies the mapping between key press and focus movement, and it should be quite self-explanatory. The default mapping is likely what you want if you are migrating from version 1. It is the equivalent of the setting direction="both"
. If you do want to make changes, such as not supporting the ArrowLeft and ArrowRight keys then you can create and use a custom key configuration:
import { DEFAULT_KEY_CONFIG, Key } from "react-roving-tabindex";
export const CUSTOM_KEY_CONFIG = {
...DEFAULT_KEY_CONFIG, // Copy the default configuration.
[Key.ARROW_LEFT]: undefined, // Or use null.
[Key.ARROW_RIGHT]: undefined // Or use null.
};
const SomeComponent = () => (
<RovingTabIndexProvider keyConfig={CUSTOM_KEY_CONFIG}>
{/* Whatever here */}
</RovingTabIndexProvider>
);
The third and final breaking change is that the optional third argument to the useRovingTabIndex
hook has changed type from a string ID to an object. This change only affects you if you have needed to pass your own IDs to the hook, in particular if you support server-side rendering (SSR). So if you have used the following...
const [...] = useRovingTabIndex(ref, true, id);
// ^^
... then you will need to instead pass the ID in an object:
const [...] = useRovingTabIndex(ref, true, { id });
// ^^^^^^
Note that it is fine to create a new object for that third argument each time the containing component is rendered; by itself that will not trigger a re-render. As a reminder, the assigned ID will be captured on mounting of the containing component and cannot be changed during that component's lifetime.
The version 1 release has no breaking changes compared to v0.9.0. The version bump was because the package had matured.
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.
@types/styled-components
package is currently downgraded to v4.1.8 because of this issue. This only affects the Storybook build.FAQs
React implementation of a roving tabindex, now with grid support
The npm package react-roving-tabindex receives a total of 12,738 weekly downloads. As such, react-roving-tabindex popularity was classified as popular.
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
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.
Security News
The Linux Foundation is warning open source developers that compliance with global sanctions is mandatory, highlighting legal risks and restrictions on contributions.
Security News
Maven Central now validates Sigstore signatures, making it easier for developers to verify the provenance of Java packages.