useRovingFocus
Keyboard navigation for composite widgets where arrow keys move focus between items, skipping disabled ones.
Usage
useRovingFocus manages focus across a group of items — only the active item has tabindex="0", all others have tabindex="-1". Arrow keys move focus between items, automatically skipping disabled ones. Supports linear (horizontal/vertical) and grid (2D) navigation modes.
Architecture
useRovingFocus builds on useEventListener for keydown handling. It is a standalone composable — not part of the registry/selection hierarchy — making it composable alongside createSingle or createSelection for widgets that separate focus from selection (e.g., listboxes, selects).
useRovingFocus vs useVirtualFocus
Both manage keyboard navigation, but they use different focus strategies:
useRovingFocus | useVirtualFocus | |
|---|---|---|
| DOM focus | Moves to each item | Stays on the control (e.g., <input>) |
| tabindex | Managed per item | Not used |
| ARIA | Items receive focus directly | aria-activedescendant on control |
| Use for | Toolbars, menus, grids, tabs | Comboboxes, autocompletes, searchable selects |
| Keyboard pattern | Items are real focusable elements | Items are virtual — only one DOM node has focus |
Choose useRovingFocus when items are real interactive elements (buttons, links). Choose useVirtualFocus when a single input drives a list of options that aren’t individually focusable.
Reactivity
| Property/Method | Reactive | Notes |
|---|---|---|
focusedId | ShallowRef, tracks currently focused item | |
isTabbable(id) | - | Returns true for the one item that should have tabindex="0" |
focus(id) | - | Programmatically focus an item by ID |
next() | - | Move focus to next enabled item |
prev() | - | Move focus to previous enabled item |
first() | - | Move focus to first enabled item |
last() | - | Move focus to last enabled item |
onKeydown | - | Keydown handler — auto-bound when target is provided |
Examples
FAQ
Use useRovingFocus when items are real focusable elements (buttons, links) and you want DOM focus to move between them. Use useVirtualFocus when a single input drives a list of options that aren’t individually focusable, like a combobox.
Pass columns. Left/Right step one item, Up/Down step by a full row (±columns), and Home/End jump to the row edges. Add circular: true to wrap focus around the ends.
Yes. useRovingFocus only manages focus, so compose it with a selection composable like createSingle when you need a listbox or select that tracks both focus and the chosen value.
Yes. On each keydown it reads the computed direction of the element the handler is bound to, so a dir="rtl" on the document or any ancestor (including the one useRtl writes) reverses Left/Right: ArrowRight moves to the previous item and ArrowLeft to the next, matching the visual order. Up/Down are unaffected. No plugin is required.
No — when you pass a target element, onKeydown is auto-bound to it. Omit target and bind the returned onKeydown handler to your element manually instead.