Skip to main content
Vuetify0 v1.0 is here
Vuetify0 Logo
Theme
Mode
Palettes
Accessibility
Vuetify One
Sign in to Vuetify One

Access premium tools across the Vuetify ecosystem — Bin, Play, Studio, and more.

Not a subscriber? See what's included

useRovingFocus

Keyboard navigation for composite widgets where arrow keys move focus between items, skipping disabled ones.

Edit this page
Report a Bug
Open issues
View on GitHub
Copy Markdown

PreviewIntermediateJun 29, 2026

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.

vue
<script setup lang="ts">
  import { useRovingFocus } from '@vuetify/v0'
  import { useTemplateRef } from 'vue'

  const toolbar = useTemplateRef('toolbar')

  const items = [
    { id: 'bold', label: 'Bold' },
    { id: 'italic', label: 'Italic' },
    { id: 'underline', label: 'Underline', disabled: true },
    { id: 'strike', label: 'Strikethrough' },
  ]

  const { focusedId, isTabbable } = useRovingFocus(
    () => items.map(item => ({
      id: item.id,
      el: () => toolbar.value?.querySelector(`[data-id="${item.id}"]`),
      disabled: item.disabled,
    })),
    { target: toolbar, orientation: 'horizontal' },
  )
</script>

<template>
  <div ref="toolbar" role="toolbar" aria-label="Formatting">
    <button
      v-for="item in items"
      :key="item.id"
      :data-id="item.id"
      :tabindex="isTabbable(item.id) ? 0 : -1"
      :disabled="item.disabled"
    >
      {{ item.label }}
    </button>
  </div>
</template>

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).

Roving Focus Architecture

Use controls to zoom and pan. Click outside or press Escape to close.

Roving Focus Architecture

useRovingFocus vs useVirtualFocus

Both manage keyboard navigation, but they use different focus strategies:

useRovingFocususeVirtualFocus
DOM focusMoves to each itemStays on the control (e.g., <input>)
tabindexManaged per itemNot used
ARIAItems receive focus directlyaria-activedescendant on control
Use forToolbars, menus, grids, tabsComboboxes, autocompletes, searchable selects
Keyboard patternItems are real focusable elementsItems 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/MethodReactiveNotes
focusedIdShallowRef, 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

Color Grid

A 24-swatch material color palette arranged in a 6-column grid, demonstrating useRovingFocus in 2D mode. Passing columns: 6 enables full grid navigation: left/right arrow keys step one swatch, up/down step one row (±6), Home and End jump to the first and last swatch in the current row, and Ctrl+Home/Ctrl+End jump to the absolute first and last swatch. The circular: true option wraps navigation so the focus cycles back to the start when it reaches either end.

Grid.vue is a reusable component that accepts a swatches prop and exposes v-model for the selected swatch. It registers each swatch by element reference using a querySelector on the grid container, and passes the IDs back through isTabbable to set tabindex="0" on exactly one swatch at a time. The onFocus callback fires whenever keyboard navigation lands on a swatch and immediately updates the model — so selection and keyboard focus stay in sync without a separate event handler. grid.vue wires the 24 swatches and the v-model together in a minimal entry point.

Reach for grid mode any time your items form a logical 2D structure: color pickers, emoji grids, calendar date cells, data table cells. For strictly linear focus (toolbar buttons, menu items, tabs), use orientation: 'horizontal' or 'vertical' without columns. To pair keyboard focus with selection state from a composable, see the createSingle decision table.

FileRole
Grid.vueReusable swatch grid with 2D keyboard navigation
grid.vueEntry point rendering the material color palette

FAQ

Discord
Need help? Join our community for support and discussions ↗

API Reference

The following API details are for the useRovingFocus composable.
Was this page helpful?

© 2016-1970 Vuetify, LLC
Services
Ctrl+/