RovingController
Roving tabindex with arrows, Home and End, over the items getItems() returns.
Reach for it for tabs, toolbars, lists and grids with one tab stop.
Import
import { RovingController, createRovingController } from '@ecopages/radiant-ui/controllers';Try it
One button is tabbable. Focus it and press ArrowRight, ArrowLeft, Home or End to move focus, and the tab stop moves with it.
<div data-example="roving">
<div role="toolbar" aria-label="Formatting">
<button type="button">Bold</button>
<button type="button">Italic</button>
<button type="button">Underline</button>
</div>
</div>import { RovingController } from '@ecopages/radiant-ui/controllers';
export function mount(root: HTMLElement): () => void {
const toolbar = root.querySelector<HTMLElement>('[role="toolbar"]')!;
const roving = new RovingController({
getItems: () => [...toolbar.querySelectorAll('button')],
getTarget: () => toolbar,
});
roving.attach();
roving.sync();
return () => roving.destroy();
}mount runs once for each example element on the page, and the function it returns undoes it; see how the examples run.
Config
| Option | Description |
|---|---|
getItems | The items, read on every event and sync, so the collection can change without telling the controller. |
getTarget | The element that receives keydown and focusin. |
orientation | horizontal (default), vertical or none, which handles no keys and leaves them to the component. |
wrap | Wrap past the first or last item. Default true. |
getEnabled | Return false to ignore keys and focus. |
onMove | Called after a key moved focus to an item, so the component can select it or open its menu. |
Lifecycle
attach() listens for keydown and focusin on the target. Call sync() from the host's connected callback and whenever the items change: it sets tabindex to 0 on the active item and -1 on the rest. destroy() removes the listeners and forgets the active item but leaves tabindex as it is. Also getActive(), setActive(item), focus(item) and navigate(current, key).
With a host, createRovingController(host, config) does the same on connect and disconnect; see bindController.
Protected hooks
Extend the class and override these to change how it decides. A component never extends a controller; a subclass is registered with bindController.
| Hook | Description |
|---|---|
resolveNext(items, current, key, orientation, wrap) | Decides where a key moves focus. Override to change the keys or the order, such as skipping every other item or adding PageUp and PageDown. Return { handled: false } to leave the key alone. The default is navigateRovingTabindex. |