Skip to main content
Vuetify0 is now in beta!
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

useVirtualFocus

A composable for keyboard navigation where DOM focus stays on a single control element while a visual highlight moves across items.

Usage

useVirtualFocus manages a virtual highlight across a list of items — DOM focus never leaves the control element (typically an <input>). Arrow keys move the highlight, aria-activedescendant on the control references the active item, and data-highlighted is set on the highlighted element. This is the standard pattern for comboboxes, autocompletes, and searchable selects.

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

  const input = useTemplateRef('input')
  const list = useTemplateRef('list')

  const options = [
    { id: 'opt-1', label: 'Option 1' },
    { id: 'opt-2', label: 'Option 2' },
    { id: 'opt-3', label: 'Option 3', disabled: true },
    { id: 'opt-4', label: 'Option 4' },
  ]

  const { highlightedId } = useVirtualFocus(
    () => options.map(item => ({
      id: item.id,
      el: () => list.value?.querySelector(`[data-id="${item.id}"]`),
      disabled: item.disabled,
    })),
    { control: input, orientation: 'vertical' },
  )
</script>

<template>
  <div>
    <input
      ref="input"
      aria-controls="listbox"
      aria-expanded="true"
      role="combobox"
    />

    <ul id="listbox" ref="list" role="listbox">
      <li
        v-for="item in options"
        :id="item.id"
        :key="item.id"
        :data-id="item.id"
        role="option"
      >
        {{ item.label }}
      </li>
    </ul>
  </div>
</template>

Architecture

useVirtualFocus shares its traversal kernel with useRovingFocus — both build on createFocusTraversal. The difference: roving focus moves real DOM focus between items, while virtual focus keeps DOM focus on a control and moves a data attribute highlight.

Virtual Focus Architecture

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

Virtual Focus Architecture

Options

OptionTypeDefaultNotes
controlMaybeRefOrGetter<HTMLElement | null>Required. Element that holds DOM focus and receives aria-activedescendant
targetMaybeRefOrGetter<HTMLElement | null>controlElement to attach keydown listener to (defaults to control)
orientation'horizontal' | 'vertical' | 'both''vertical'Arrow key axis. Ignored when columns is set — grid navigation uses all 4 arrows
circularbooleanfalseWrap around when navigating past first/last item
columnsMaybeRefOrGetter<number>Column count for 2D grid navigation. Left/Right step ±1, Up/Down step ±columns, Home/End jump to row edges, Ctrl+Home/End go to first/last item
onHighlight(id: ID) => voidCalled when the highlighted item changes

Reactivity

Property/MethodReactiveNotes
highlightedIdShallowRef, tracks the currently highlighted item
highlight(id)-Programmatically highlight an item by ID
clear()-Remove the highlight
next()-Move highlight to next enabled item
prev()-Move highlight to previous enabled item
first()-Move highlight to first enabled item
last()-Move highlight to last enabled item
onKeydown-Keydown handler — auto-bound to target or control

Examples

Searchable Fruit Picker

A searchable listbox where the input keeps DOM focus. Arrow keys navigate filtered results, Enter selects the highlighted item, and disabled items are skipped.

FileRole
Listbox.vueReusable listbox component using useVirtualFocus
listbox.vueEntry point with a fruit list
  • Apple
  • Banana
  • Blueberry
  • Cherry
  • Dragon Fruit
  • Grape
  • Kiwi
  • Lemon
  • Mango
  • Orange
  • Peach
  • Pear
  • Strawberry
  • Watermelon

API Reference

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

Ctrl+/