Options in JavaScript

Add keyboard navigation to markup you cannot change, such as a menu from a component library or CMS, with JavaScript options that select items by role.

Use options to navigate markup from a component library, CMS or other code you cannot change. This menu has no data-keyrove-* attributes; its options select items by their existing role.

Live · share menu
<div id="share" role="menu" aria-label="Share">
  <button type="button" role="menuitem" tabindex="0">Copy link</button>
  <button type="button" role="menuitem" tabindex="-1">Email a copy</button>
  <button type="button" role="menuitem" tabindex="-1" disabled>
    Export as PDF
  </button>
  <button type="button" role="menuitem" tabindex="-1">Post to Slack</button>
  <button type="button" role="menuitem" tabindex="-1">Open in Figma</button>
</div>

↑/↓ move between items and wrap at the ends. Typing finds an entry, and Tab leaves the menu. The options configure navigation; the markup supplies roles, an initial tab stop and the disabled attribute on Export as PDF.

What is coming from where

Options override attributes one setting at a time. For example, keyRove(e, { loop: true }) enables looping while still finding items from markup. See attributes and options for replacement rules and the API reference for every field.

One object, both handlers

Pass the same items, root, skip and rovingTabindex settings to both handlers. In the demo, typing P reaches Post to Slack through the same selector the arrows use.

Typeahead ignores movement settings such as keys, columns and looping. Use its label option when an item's text is not its search label. Create the handler once per listener.

Items, by selector or by hand

items takes a selector, run inside the group's root on each keypress, or a function returning the elements:

keyRove(e, { items: '[role="menuitem"]' });
keyRove(e, { items: (root) => [...root.querySelectorAll('.row')].reverse() });

Either way the elements are the group's sequence in the order given, and disabled ones are left out — the same rule the attribute reading follows, because it is applied to the items rather than to the reading that found them. A skip test passes items over while keeping them in the sequence, which is what a grid needs: a skipped cell holds its column.

keyRove(e, { items: '.cell', skip: '.cell-empty', cols: 4 });

Where this belongs

Use options when you cannot edit the markup or need a computed setting: keyRove(e, { items: '.cell', cols: columnsNow() }) reads the column count on each call. For CSS grids, cols: 'auto' or data-keyrove-cols="auto" reads it from the layout; see responsive grid.

Search documentation

↑ ↓ to selectEnter to openEsc to close