# keyrove > Keyboard navigation for lists, grids and trees. Configure it with data attributes or JavaScript options, in any framework.
# Keyboard navigation that behaves itself. Mark the items and pass `keydown` to `keyRove`. Lists, grids and trees then move focus the way keyboard users expect, in any framework.
[Get started](/docs/introduction) [Examples](/docs/examples) npm i @mixedrays/keyrove
Live · mail folders
```html copy ``` ```ts selected import { keyRove } from '@mixedrays/keyrove'; // whole setup const menu = document.querySelector('#menu'); menu.addEventListener('keydown', (e) => keyRove(e)); ```

↑ ↓ move, Home and End jump. Tab still visits each folder.

01 / Your stack

## Use it in your framework The same items, the same handler. `keyRove` accepts native keyboard events and compatible framework events, including React's synthetic events. There is no adapter or wrapper component to install. Start with ordinary attributes. When you want type-checked settings, [attribute builders](/docs/api#attribute-builders) fit the same templates. [Framework setup](/docs/installation)
```ts title="Vanilla" copy import { keyRove } from '@mixedrays/keyrove'; const menu = document.querySelector('#menu'); menu.addEventListener('keydown', (e) => keyRove(e)); ``` ```tsx title="React" import { keyRove } from '@mixedrays/keyrove'; export const Menu = () => (
  • Inbox
  • Drafts
  • Sent
); ``` ```vue title="Vue" ``` ```svelte title="Svelte"
  • Inbox
  • Drafts
  • Sent
```

02 / Your settings

## Add only the behavior you need Keep the handler. Change a setting in the markup. **Looping** wraps from the last item to the first. **Horizontal** changes the default arrows. **One tab stop** lets Tab enter and leave the group while the arrows move within it. Each example adds just one behavior to the list you already know.
```html
  • Inbox
  • Drafts
  • Sent
``` ```ts import { keyRove } from '@mixedrays/keyrove'; const folders = document.querySelector('#folders-loop'); folders.addEventListener('keydown', (e) => keyRove(e)); ``` Press ↓ on Sent to return to Inbox. [Looping lists](/docs/examples/looping-lists)
```html
  • Inbox
  • Drafts
  • Sent
``` ```ts import { keyRove } from '@mixedrays/keyrove'; const folders = document.querySelector('#folders-horizontal'); folders.addEventListener('keydown', (e) => keyRove(e)); ``` Use ← and → to move between folders. [Horizontal lists](/docs/examples/horizontal-lists)
```html
  • Inbox
  • Drafts
  • Sent
``` ```ts import { keyRove } from '@mixedrays/keyrove'; const folders = document.querySelector('#folders-roving'); folders.addEventListener('keydown', (e) => keyRove(e)); ``` Move with the arrows, then press Tab to leave the group. [Roving tabindex](/docs/examples/roving-tabindex)

03 / Your markup

## Configure it where it fits Write attributes when you own the markup. Use an item selector when the HTML comes from a component library or CMS. These two setups navigate identically. You can mix both: `keyRove(e, { loop: true })` overrides looping while still reading the items and other settings from their attributes. [Attributes and options](/docs/attributes-and-options)
```html title="Attributes" copy
  • Inbox
  • Drafts
  • Sent
``` ```html title="Options"
  • Inbox
  • Drafts
  • Sent
```

04 / Your layout

## Move through a grid Give the group a column count and the same handler moves across cells and between rows. Your CSS still controls the layout. For a responsive CSS grid, set `data-keyrove-cols="auto"`. keyrove reads the current tracks on each keypress, so resizing needs no navigation instance to update. [Grid navigation](/docs/examples/grid)
```html
``` ```ts import { keyRove } from '@mixedrays/keyrove'; const slots = document.querySelector('#slots'); slots.addEventListener('keydown', (e) => keyRove(e)); ``` Use all four arrows. Home and End jump to the ends of a row.
```html
``` ```ts selected import { keyRove } from '@mixedrays/keyrove'; const months = document.querySelector('#months'); months.addEventListener('keydown', (e) => keyRove(e)); ``` Drag the preview’s lower-right corner to resize it, then try the arrows. [Responsive grid](/docs/examples/responsive-grid)

05 / Your interaction

## Combine behaviors Now put the pieces together: a menu configured with an item selector, looping, and one tab stop. Add `createTypeahead` when people should also be able to find an item by its label. Both handlers share the same group settings; `||` passes a key to typeahead only when navigation leaves it unhandled. [JavaScript options](/docs/examples/javascript-options) · [Typeahead](/docs/examples/typeahead)
```html ``` ```ts selected import { createTypeahead, keyRove } from '@mixedrays/keyrove'; const config = { items: '[role="menuitem"]', loop: true, rovingTabindex: true, }; const typeahead = createTypeahead(config); const menu = document.querySelector('#share'); menu.addEventListener('keydown', (e) => keyRove(e, config) || typeahead(e)); ```

Type E to focus Email a copy. Arrows skip the disabled item and wrap at the ends.

06 / Your application

## Build more involved interfaces Give an item a shortcut. Nest groups with their own keys. Or combine focus navigation with your application's behavior. The tree is the fullest example: keyrove navigates visible rows and typeahead finds them by name. Your code opens and closes folders, sets ARIA states, and decides what activating a file does. Start with the part you need; the same handler stays underneath.
```html ``` ```ts import { keyRove } from '@mixedrays/keyrove'; const tools = document.querySelector('#tools'); tools.addEventListener('keydown', (e) => keyRove(e)); ``` Press P for Pen, then ↓ to continue. [Focus keys](/docs/examples/focus-keys)
```html
  • Reply
  • Reply in thread
  • Copy link
  • Delete message
``` ```ts import { keyRove } from '@mixedrays/keyrove'; const actions = document.querySelector('#message-actions'); // One listener also serves the nested reaction row. actions.addEventListener('keydown', (e) => keyRove(e)); ``` Use ↑/↓ in the menu. Focus a reaction to try ←/→; Esc leaves its group. [Nested roots](/docs/examples/nested-roots)
```html
  • src
    • main.ts
  • package.json
  • README.md
  • tsconfig.json
``` ```ts selected import { createTypeahead, keyRove } from '@mixedrays/keyrove'; const tree = document.querySelector('#files'); const config = { items: '[role="treeitem"]', skip: '[hidden] [role="treeitem"]', rovingTabindex: true, }; const typeahead = createTypeahead(config); // branch is your application's expand/collapse handler. // The full tree example includes its implementation. tree.addEventListener( 'keydown', (e) => keyRove(e, config) || typeahead(e) || branch(e), ); ``` Use → to open a folder and ← to close it. Type a file name to jump. [Full tree implementation](/docs/examples/tree-view#a-full-tree-view)
## What it leaves to you keyrove moves focus and nothing else, so it fits whatever widget you are building.
### keyrove does - Finds the next item and focuses it - Prevents the browser's default only for keys it handles - Moves the tab stop with focus when roving tabindex is on
### You do - Roles, labels and ARIA states - Selection, and what Enter and Space do - Focusable items and the first tab stop
The [listbox example](/docs/examples/listbox) puts all three together.
## Start with a list Install the package, mark the items, and pass `keydown` to `keyRove`.
[Get started](/docs/introduction) npm i @mixedrays/keyrove
### [Installation](/docs/installation) Setup for vanilla JavaScript, React, Vue and Svelte.
### [Examples](/docs/examples) Live demos, from a basic list to a tree view.
### [API reference](/docs/api) Every handler, option, key binding and type.
### [AI prompts](/docs/ai-prompts) Point a coding assistant at these docs.