Looping lists

Make keyboard navigation wrap from the last item to the first and back again, and decide when a list should loop and when it should stop at its ends.

By default, navigation stops at the first and last items. Add data-keyrove-loop to the container to wrap between them:

  • ↓ from Sign out moves to Profile.
  • ↑ from Profile moves to Sign out.

You can sign out any time you like, but the arrows will never leave. Tab will.

Live · account menu
  • Profile
  • Settings
  • Keyboard shortcuts
  • Help
  • Sign out
<ul id="account-menu" data-keyrove-loop>
  <li data-keyrove-item tabindex="0">Profile</li>
  <li data-keyrove-item tabindex="0">Settings</li>
  <li data-keyrove-item tabindex="0">Keyboard shortcuts</li>
  <li data-keyrove-item tabindex="0">Help</li>
  <li data-keyrove-item tabindex="0">Sign out</li>
</ul>

keyrove reads the attribute on every keypress. You can turn looping on or off without changing the listener or reinitializing the group.

Boolean value

Both data-keyrove-loop and data-keyrove-loop="true" enable looping. Set data-keyrove-loop="false" to disable it. The same rule applies to all boolean keyrove attributes, including data-keyrove-item, data-keyrove-root, data-keyrove-skip, and data-keyrove-roving-tabindex.

In JSX, pass a boolean directly:

<ul data-keyrove-loop={loop}>…</ul>

Entering at either end

When no item is focused and the group's listener receives a directional key, keyrove focuses the first navigable item. In a looping list, prev enters at the last navigable item instead. With the default bindings:

  • ↓ enters at the first item.
  • ↑ enters at the last item.

This matches the optional arrow-key entry behavior in the APG menu button pattern.

Home, End, and the page keys still require focus inside an item.

Only next and prev wrap

Looping changes only next and prev:

  • Home and End move to the first and last navigable items.
  • PageUp and PageDown stop at either end if a jump would go past it.

If you rebind next and prev to J and K, those keys wrap and the arrows return to their browser defaults. See custom keys.

Skipped items keep their place

A wrap lands on the first or last navigable item, passing over items marked data-keyrove-skip or disabled. See skipped items.

Nowhere to go

If the only navigable item already has focus, next and prev leave it there. keyrove still calls preventDefault(), but onMove does not fire. For next, the return value is { action: 'next', from, to: null }, just as at the end of a non-looping list.

Grids keep their edges

Looping applies only to lists. When data-keyrove-cols is greater than 1, the group is a grid and ignores data-keyrove-loop. Cell moves continue across row boundaries, but stop at the first or last cell; row moves stop at the top or bottom. At these edges, keyrove prevents the browser's default action without moving focus. See the APG grid pattern for keyboard navigation guidance.

When a list should wrap

Wrapping works well for short menus, pickers, and tab lists where both ends are visible.

For long, scrolling lists, keeping the ends helps users track their position. Wrapping can unexpectedly jump the viewport back to the top and may be hard to notice with a screen reader. Without looping, holding ↓ at the bottom keeps focus there and prevents the page from scrolling.

Search documentation

↑ ↓ to selectEnter to openEsc to close