roving-tab-index
NPM 1.9.0
Description
tabindex=0 element, while the individual elements maintain tabindex=-1 and are made accessible via arrow keys after the entry element if focused. This allows keyboard users to quickly tab through a page without having to stop on every element in a large collection. Attaching a RovingTabindexController to your custom element will manage the supplied elements via this pattern.
Usage
yarn add @spectrum-web-components/reactive-controllers
Import the RovingTabindexController via:
import { RovingTabindexController } from '@spectrum-web-components/reactive-controllers/RovingTabindex.js';
Example
A Container element that manages a collection of <sp-button> elements that are slotted into it from outside might look like the following:
import { html, LitElement } from 'lit'; import { RovingTabindexController } from '@spectrum-web-components/reactive-controllers/RovingTabindex.js'; import type { Button } from '@spectrum-web-components/button'; class Container extends LitElement { rovingTabindexController = new RovingTabindexController() < Button > (this, { elements: () => [...this.querySelectorAll('sp-button')], }); render() { return html` <slot></slot> `; } }
The above will default to entering the Container element via the first <sp-button> element every time while making all slotted <sp-button> elements accessible via the the arrow key (ArrowLeft, ArrowRight, ArrowUp, and ArrowDown) managed tab order.
Options
A Container can further customize the implementation of the RovingTabindexController with the following options:
- directionto customize how and which arrow keys manage what element is to be focused and accepts a either a string of- both,- vertical,- horizontal, or- gridor a method returning one of those strings
- elementEnterActionenacts actions other than- focuson the entered element which accepts a method with a signature of- (el: T) => void
- elementsprovides the elements that will have their- tabindexmanaged via a method with a signature of- () => T[]
- focusInIndexto control what element will recieve- tabindex=0while focus is outside of the- Containerand accepts a method with a signature of- (_elements: T[]) => number
- isFocusableElementdescribes the state an element much be in to receive- focusvia a method with a signature of- (el: T) => boolean
- listenerScopeoutlines which parts on a container's DOM when listening for arrow key presses via an element reference or a method returning an element reference with the signature- () => HTMLElement
Advanced usage
These options can be combined to form various interfaces from the more default that we saw above to the very complex. Below is another Container that manages slotted <sp-button> elements via the RovingTabindexController. The options provided ensure:
- the first focused <sp-button>is the oneselectedby the container
- the elements are only focused via the ArrowLeftandArrowRightkeys
- when an element is focused it becomes the selectedelement
- only enabled elements are focusable
import { html, LitElement } from 'lit'; import { RovingTabindexController } from '@spectrum-web-components/reactive-controllers/RovingTabindex.js'; import type { Button } from '@spectrum-web-components/button'; class Container extends LitElement { rovingTabindexController = new RovingTabindexController<Button>(this, { focusInIndex: (buttons) => return this.selected ? buttons.indexOf(this.selected) : 0, direction: 'horizontal', elementEnterAction: (button) => this.selected = button, elements: () => [...this.querySelectorAll('sp-button')], isFocusableElement: (button) => !button.disabled, }); selected!: Button; render() { return html`<slot></slot>`; } }
The above usage is very close to what can be seen in the <sp-radio-group> element