# keyrove > Keyboard navigation for lists, grids and trees. Configure it with data attributes or JavaScript options, in any framework. The full text of every page indexed by https://keyrove.pages.dev/llms.txt, in the same order. --- Source: https://keyrove.pages.dev/docs/introduction # Introduction > Add keyboard navigation to lists, grids and trees with one keydown handler, then choose keys and options while native Tab behavior keeps working. keyrove moves focus through lists, grids and trees. Mark the items you want to navigate and pass `keydown` events to `keyRove`. It finds and focuses the next item without rendering UI or wrapping your components. ```html title="Markup" ``` ```ts title="The call" import { keyRove } from '@mixedrays/keyrove'; const menu = document.querySelector('#menu')!; menu.addEventListener('keydown', (e) => keyRove(e)); ``` ```ts title="No attributes" // Keep
  • , but select items without data-keyrove-item. import { keyRove } from '@mixedrays/keyrove'; const menu = document.querySelector('#menu')!; menu.addEventListener('keydown', (e) => keyRove(e, { items: 'li' })); ``` This list supports arrows, Home, End and page jumps. Tab still visits each item. You can [change the keys](#which-keys-move-focus) and configure the group with [attributes, options, or both](#where-a-group-is-described). ## How it works 1. **Items** are the elements marked with `data-keyrove-item`, read in DOM order. An `items` option can select them instead. 2. **The root** is the nearest element at or above the event target marked with `data-keyrove-root`, or the listener's element if none is marked. It contains the items and carries the group's settings. 3. **The handler**, `keyRove(event)`, reads the key, finds the target item and focuses it. It calls `preventDefault()` for handled keys, so navigation does not also scroll the page. Unhandled keys keep their browser defaults. The handler reads the current DOM and settings on every keypress. Changing items or adding a column count requires no navigation instance to update. ## What it handles | Feature | Behavior | Example | | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | | Lists | ↑/↓ move one item in DOM order | [Basic list](/docs/examples/basic) | | Grids | ↑/↓ move a row; ←/→ move a cell | [Grid](/docs/examples/grid) | | First and last items | Home/End jump to list ends or grid row ends; Ctrl+Home/End jump to grid ends | [Grid](/docs/examples/grid) | | Page jumps | PageUp/PageDown move 10 items or rows by default | [Page length](/docs/examples/basic#page-length) | | Horizontal lists | ←/→ follow the text direction | [Horizontal lists](/docs/examples/horizontal-lists) | | Looping | Next/previous wrap at list ends | [Looping lists](/docs/examples/looping-lists) | | Roving tabindex | One tab stop follows focus within a group | [Roving tabindex](/docs/examples/roving-tabindex) | | Skipped items | Pass over items marked with `data-keyrove-skip` or `disabled` | [Skipped items](/docs/examples/skipped-items) | | Nested groups | Each group has its own keys, with optional enter and exit bindings | [Nested roots](/docs/examples/nested-roots) | | Focus shortcuts | Focus an item or panel from anywhere under the listener | [Focus keys](/docs/examples/focus-keys) | | Typeahead | Add `createTypeahead()` to focus items by typing their labels | [Typeahead](/docs/examples/typeahead) | | Trees | Navigate visible rows; your handlers expand and collapse branches | [Tree view](/docs/examples/tree-view) | | Moves from code | Call `rove(list, 'next')` from a button, gamepad or remote | [`rove`](/docs/api#rove-element-action-options) | Text fields, selects and editable content keep their editing keys. See [editable targets](/docs/examples/editable-targets) for the rules and exceptions. ## Where a group is described Use `data-keyrove-*` attributes in your markup or pass options to the handler. Options are useful when the HTML comes from a component library or CMS: ```ts import { keyRove } from '@mixedrays/keyrove'; const config = { items: '[role="menuitem"]', loop: true }; const menu = document.querySelector('#share')!; menu.addEventListener('keydown', (e) => keyRove(e, config)); ``` Options override attributes one setting at a time. For example, `keyRove(e, { loop: true })` enables looping while reading items and key bindings from the markup. See [attributes and options](/docs/attributes-and-options) for the full mapping, or [options in JavaScript](/docs/examples/javascript-options) for a complete demo. ## Which keys move focus Lists use `ArrowDown` for next and `ArrowUp` for previous by default. Set `data-keyrove-next-key` and `data-keyrove-prev-key` on the root to use other `KeyboardEvent.code` values or combinations such as `mod+KeyJ`: ```html
      …
    ``` Here, J and K move focus, and the arrows return to their browser defaults. Each group can have different bindings. Home, End, PageUp and PageDown can also be rebound with their own key attributes. See [custom keys](/docs/examples/custom-keys) for examples and the [API reference](/docs/api#keys) for defaults, syntax and precedence. ## Tab still works keyrove uses `element.focus()`, preserving the browser's focus styling, scrolling and focus announcements. An item also counts as focused when a link, button or other element inside it has focus. With the default bindings, Tab, Shift+Tab, Enter, Space and Escape keep their usual behavior. Items with `tabindex="0"` remain ordinary tab stops, reachable with both Tab and the bound navigation keys. For a single tab stop per group, use [roving tabindex](/docs/examples/roving-tabindex). Tab enters and leaves the group, while the bound keys move between its items. ## What it leaves to you - **Roles and ARIA.** Set the roles, labels and states your widget needs. keyrove does not write `role`, `aria-selected` or `aria-activedescendant`. - **Selection and activation.** Decide what clicks, Enter and Space do, and add your own handlers. They are unbound by default. - **Initial tab stops.** Make items focusable in your markup. For a roving group, give one item `tabindex="0"` and the others `-1`, or call [`initRovingTabindex`](/docs/api#initrovingtabindex-root-options) after rendering. The [listbox example](/docs/examples/listbox) combines navigation with all three. ## Framework support `keyRove` accepts native keyboard events and compatible framework events, including React's synthetic events. See [`KeyRoveEvent`](/docs/api#keyroveevent) for the required shape. [Installation](/docs/installation) shows setup in vanilla JavaScript, React, Vue and Svelte. Start with the [basic list](/docs/examples/basic) to try it. --- Source: https://keyrove.pages.dev/docs/installation # Installation > Install keyrove from npm and add arrow-key navigation in vanilla JavaScript, React, Vue or Svelte, with typed attribute builders or hand-written attributes. ## Install ```sh title="pnpm" pnpm add @mixedrays/keyrove ``` ```sh title="npm" npm install @mixedrays/keyrove ``` ```sh title="yarn" yarn add @mixedrays/keyrove ``` The package is ESM-only, ships its own types, and has no runtime dependencies. ## Vanilla Attach one listener to the container. The container is the navigation root, so the item query is scoped to it automatically. ```ts import { keyRove } from '@mixedrays/keyrove'; const list = document.querySelector('#menu'); list.addEventListener('keydown', (e) => keyRove(e)); ``` ```html ``` Give non-native items `tabindex="0"` so they can receive focus. With the default bindings, Tab visits each item. For one tab stop per group, use [roving tabindex](/docs/examples/roving-tabindex); the [complete roving setup](#complete-roving-setup) below puts every piece together. Lists use ↑/↓ by default. Set `data-keyrove-next-key` and `data-keyrove-prev-key` on the root to [change the keys](/docs/examples/custom-keys). Attribute names are also exported as [constants](/docs/api#constants). Use options when you cannot add attributes to the markup. For example, `keyRove(e, { items: '[role="menuitem"]' })` selects items by their role. See [attributes and options](/docs/attributes-and-options) for the mapping and fallback rules. ## React Use [`rootAttributes` and `itemAttributes`](/docs/api#attribute-builders) for typed settings in markup. They include the root and item markers, and turn booleans into `"true"` or `"false"`. Hand-written attributes work too. ```tsx title="Typed builders" import { itemAttributes, keyRove, rootAttributes } from '@mixedrays/keyrove'; export const Menu = ({ items, loop = false }) => (
      {items.map((item) => (
    • {item.label}
    • ))}
    ); ``` ```tsx title="Hand-written attributes" import { keyRove } from '@mixedrays/keyrove'; export const Menu = ({ items, loop = false }) => (
      {items.map((item) => (
    • {item.label}
    • ))}
    ); ``` React's `SyntheticEvent` satisfies the shape `keyRove` needs, so it can be passed as the handler directly. ## Vue ```vue title="Typed builders" ``` ```vue title="Hand-written attributes" ``` ## Svelte ```svelte title="Typed builders"
      {#each items as item (item.id)}
    • {item.label}
    • {/each}
    ``` ```svelte title="Hand-written attributes"
      {#each items as item (item.id)}
    • {item.label}
    • {/each}
    ``` ## Several groups, one listener Mark each group with `data-keyrove-root` to use one listener on a shared panel or `document`. Each event uses the nearest root at or above its target, falling back to the listener's element when no root is marked. Sibling roots keep their items and settings separate. ```html
    • Inbox
    • Drafts
    • Work
    • Travel
    ``` ```ts document.querySelector('#panel').addEventListener('keydown', (e) => keyRove(e)); ``` Roots can also nest. Each inner group uses its own keys and settings; see [nested roots](/docs/examples/nested-roots). ## Complete roving setup A group with one tab stop combines four pieces: navigation, an initial tab stop, focus tracking and, optionally, typeahead. This recipe wires them to one configuration object: ```html
    ``` ```ts import { createTypeahead, followFocus, initRovingTabindex, keyRove, } from '@mixedrays/keyrove'; const list = document.querySelector('#choices')!; const config = { items: 'button', rovingTabindex: true }; // Created once, so its buffer lasts between keypresses. const typeahead = createTypeahead(config); initRovingTabindex(list, config); list.addEventListener('keydown', (e) => keyRove(e, config) || typeahead(e)); list.addEventListener('focusin', (e) => followFocus(e, config)); ``` - `initRovingTabindex` gives the first button `tabindex="0"` and the others `-1`, so Tab enters the group once. - `keyRove` moves focus with the arrows, Home, End and the page keys, and carries the tab stop with each move. - `followFocus` moves the tab stop when focus arrives another way: a click, `element.focus()`, or Tab onto a control inside an item. - `typeahead` focuses the first button whose label starts with the typed text. Without typeahead, remove `createTypeahead` from the import, the `typeahead` line and `|| typeahead(e)`. Every helper receives the same `config`, so they agree on which elements are items and that the group has one tab stop. ### After rendering Call `initRovingTabindex(list, config)` again after a render that may add, remove or replace items. It keeps a stop that is still valid and repairs a missing one. The listeners stay attached to `list`, so add them once, outside the render. In a framework, make the call from the hook that runs after each render. ### Choosing the first stop Pass `initial` when your code knows which item should start with the stop, such as the one your app shows as selected: ```ts initRovingTabindex(list, { ...config, initial: list.querySelector('.selected'), }); ``` `initial` overrides the current stop. Use it on first setup or when your app changes the selection, and leave it out of the post-render call; otherwise every render sends the stop back and the user loses their place. This recipe covers focus movement only. Roles, labels and selection belong to the widget; the [listbox](/docs/examples/listbox) adds them to these pieces. --- Source: https://keyrove.pages.dev/docs/ai-prompts # AI prompts > Copy-ready prompts that point a coding assistant at these docs, from adding keyrove to a project to building a playground. An assistant may not know keyrove's current API. Each prompt below first points the assistant at [llms.txt](/llms.txt) and the Markdown version of the pages it needs, so it works from the current docs. Paste a prompt into an assistant that can fetch URLs and, for work in an existing project, read your files. Replace anything in `[brackets]` first. If the assistant cannot fetch URLs, open the pages the prompt names and paste them in with **Copy page**, under each page's title. ## Get started ### Add keyrove to your project The assistant surveys the codebase, proposes where keyrove fits, and waits for you to choose before it changes anything. ```text copy I want to add keyboard navigation to this project with keyrove (@mixedrays/keyrove). Before writing code, read the docs index at https://keyrove.pages.dev/llms.txt, then https://keyrove.pages.dev/docs/introduction.md and https://keyrove.pages.dev/docs/installation.md. Use those pages, not memory, for API names and behavior. 1. Work out the framework, package manager and component conventions this project uses. 2. Find components where arrow-key navigation would help: menus, lists, toolbars, tab lists, card grids, trees, sidebars, and any keydown handlers that already move focus by hand. List them with file paths and the keyrove pattern each would use: list, grid, roving tabindex, nested roots or typeahead. 3. Stop and let me pick one before changing anything. 4. For the one I pick, install the package with the project's package manager, add the handler and item markers, and keep the existing roles, ARIA attributes and click behavior. keyrove only moves focus; roles, selection and activation stay with the component. 5. Tell me which keys now work and how to try them. ``` ### Build a playground A throwaway app with one demo per feature and a log of every move. The React version is a Vite project; the single-file version runs straight from disk with no install and needs an internet connection to load keyrove from a CDN. ```text title="React" copy Build me a playground for trying keyrove (@mixedrays/keyrove), a keyboard navigation library. Read https://keyrove.pages.dev/llms.txt first, then https://keyrove.pages.dev/docs/installation.md and https://keyrove.pages.dev/docs/api.md. Use only APIs those pages document. Make it a Vite + React + TypeScript app: one page, with a section per demo, each captioned with the keys to press. - A vertical list with the arrows, Home and End - A looping list - A horizontal toolbar - A grid with a fixed column count, and a CSS grid using cols "auto" - A list with skipped and disabled items - A roving-tabindex list with typeahead, using initRovingTabindex, followFocus and createTypeahead - A nested group with enter and exit keys Add a settings panel for loop, orientation, columns and custom next/prev keys, applied to the demos with rootAttributes so a change takes effect on the next keypress. Log every move from onMove (action, from, to) in a panel at the bottom. Keep the styling plain, with a clearly visible focus ring. Finish with the commands to install and run it. ``` ```text title="One HTML file" copy Build a single HTML file for trying keyrove (@mixedrays/keyrove), a keyboard navigation library, with a CDN dependency and no build step. Read https://keyrove.pages.dev/llms.txt first, then https://keyrove.pages.dev/docs/introduction.md and https://keyrove.pages.dev/docs/api.md. Use only APIs those pages document. Import the package as an ES module from a CDN such as https://esm.sh/@mixedrays/keyrove, in a