Basic list

Add keyboard navigation to a list with one keydown listener, keep its normal tab order, and react to each move with onMove.

Add data-keyrove-item and tabindex="0" to each list item, then attach a keydown listener to the container.

Tab to an item or click it to try the demo:

  • ↑ / ↓ move one item.
  • Home / End jump to the first or last item.
  • PageUp / PageDown move in blocks.
Live · countries
  • Argentina
  • Australia
  • Belgium
  • Brazil
  • Canada
  • Denmark
  • Finland
  • France
  • Germany
  • India
  • Ireland
  • Italy
<ul id="countries" data-keyrove-page-length="5">
  <li data-keyrove-item tabindex="0">Argentina</li>
  <li data-keyrove-item tabindex="0">Australia</li>
  <li data-keyrove-item tabindex="0">Belgium</li>
  <!-- … -->
  <li data-keyrove-item tabindex="0">Italy</li>
</ul>

Tab still visits every item. Use roving tabindex to make the list a single tab stop, or custom keys to change the bindings.

Page length

Set data-keyrove-page-length on the container to choose how many items PageUp and PageDown move. The default is 10; this twelve-item demo uses 5. Jumps stop at the first or last item.

At either end, pressing a key to move farther keeps focus in place and prevents the browser's default action. See looping lists to wrap to the other end.

Reacting to movement

Pass onMove in the second argument to run a callback after focus moves. It only fires when focus actually changes. This argument also accepts settings in place of HTML attributes.

list.addEventListener('keydown', (e) => {
  keyRove(e, {
    onMove: ({ action, to }) => console.log(action, '→', to),
  });
});

The readout at the top of the demo shows three outcomes:

  • Indigo: focus moved; onMove fired.
  • Amber: keyrove handled the key, but focus stayed in place.
  • Gray: keyrove left the key to the browser.

Amber and gray readouts use keyRove's return value. Other handlers can use it to check whether keyrove handled the key.