roving-tab-index
NPM
1.0.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:
direction
to customize how and which arrow keys manage what element is to be focused and accepts a either a string ofboth
,vertical
,horizontal
, orgrid
or a method returning one of those stringselementEnterAction
enacts actions other thanfocus
on the entered element which accepts a method with a signature of(el: T) => void
elements
provides the elements that will have theirtabindex
managed via a method with a signature of() => T[]
focusInIndex
to control what element will recievetabindex=0
while focus is outside of theContainer
and accepts a method with a signature of(_elements: T[]) => number
isFocusableElement
describes the state an element much be in to receivefocus
via a method with a signature of(el: T) => boolean
listenerScope
outlines 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 oneselected
by the container - the elements are only focused via the
ArrowLeft
andArrowRight
keys - when an element is focused it becomes the
selected
element - 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