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.

Get started Try the demos

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.

Waiting for a keypress…
<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.