# Installation > Installing the package, wiring the first keydown handler in any framework, and what the markup has to carry. ## Install ```sh pnpm add @mixedrays/keyrove ``` ```sh npm install @mixedrays/keyrove ``` ```sh 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 ``` The `tabindex="0"` is yours to put there — keyrove moves focus but never makes an element focusable. It is also what keeps these items reachable by Tab, which keyrove leaves alone; if you would rather the group were a single tab stop, that is [roving tabindex](/docs/examples/roving-tabindex). By default the group answers to and . Add `data-keyrove-next-key` / `data-keyrove-prev-key` to the root for anything else — see [custom keys](/docs/examples/custom-keys). ## React ```tsx import { keyRove } from '@mixedrays/keyrove'; export const Menu = ({ items }) => ( ); ``` React's `SyntheticEvent` satisfies the shape `keyRove` needs, so it can be passed as the handler directly. ## Vue ```vue ``` ## Svelte ```svelte ``` ## Several groups, one listener The navigation root is the nearest ancestor carrying `data-keyrove-root`, falling back to the element the listener is attached to. Marking each group as a root lets a single delegated listener — on a panel, or on `document` — serve any number of independent groups without them seeing each other's items. ```html
``` ```ts document.querySelector('#panel').addEventListener('keydown', (e) => keyRove(e)); ``` Roots can also sit inside one another, which is how a group that is part of another group's flow keeps its own keys and columns — see [nested roots](/docs/examples/nested-roots). ## Attribute constants Every attribute name is exported as a constant, so markup built in JavaScript does not have to hardcode strings. ```ts import { KEYROVE_ATTR_ITEM, KEYROVE_ATTR_SKIP } from '@mixedrays/keyrove'; const item = document.createElement('li'); item.setAttribute(KEYROVE_ATTR_ITEM, ''); item.tabIndex = 0; ``` The full list is in the [API reference](/docs/api#attributes).