Framework-agnostic · Zero dependencies
Keyboard navigation, driven by data attributes.
Mark your items with data-keyrove-item, pass keydown events to keyRove, and
lists and grids become keyboard navigable. Arrows are the default binding, not
the limit — the keys that move focus are attributes, so any key can drive a
group, and Tab keeps behaving the way the browser
intends.
pnpm add @mixedrays/keyrove
No framework, no adapter
keyRove takes anything shaped like a keydown event, so the same call works
with a native listener or a React, Vue, or Svelte synthetic event.
Any key, not just arrows
data-keyrove-next-key and data-keyrove-prev-key take any
KeyboardEvent.code. ArrowDown and ArrowUp are what you get for free, not
what you are stuck with.
Tab is left alone
Focus moves natively, and preventDefault() is called only for keys keyrove is
bound to — so Tab and
Shift+Tab keep working beside it.
Grid-aware
Declare data-keyrove-cols-length and Up/Down move a whole row while
Left/Right move a cell, with no wrapping at the edges.
Three lines and a list is navigable
Give each navigable element data-keyrove-item and a tab stop, then hand the
container's keydown event to keyRove. Try it — Tab to
an item, then use the arrow keys.
<ul id="menu">
<li data-keyrove-item tabindex="0">Inbox</li>
<li data-keyrove-item tabindex="0">Drafts</li>
<li data-keyrove-item tabindex="0">Sent</li>
<li data-keyrove-item tabindex="0">Spam</li>
<li data-keyrove-item tabindex="0">Trash</li>
</ul>
import { keyRove } from '@mixedrays/keyrove';
document.querySelector('#menu').addEventListener('keydown', (e) => {
keyRove(e);
});
Or bind the keys you want
The same list, driven by J and K instead — one attribute each, and nothing in the JavaScript changes.
<ul id="menu" data-keyrove-next-key="KeyJ" data-keyrove-prev-key="KeyK">
<li data-keyrove-item tabindex="0">Inbox</li>
<li data-keyrove-item tabindex="0">Drafts</li>
</ul>
Read the introduction for how it fits together, see custom keys for the rebinding rules, or jump straight to the API reference.