Skip to main content
You are viewing Pre-Alpha documentation.
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.


IntermediateMar 18, 2026

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

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.

Functions

useVirtualFocus

(items: () => VirtualFocusItem[], options: VirtualFocusOptions) => VirtualFocusReturn

Options

control required

MaybeRefOrGetter<HTMLElement | null | undefined>

Element that retains DOM focus and receives aria-activedescendant

target

MaybeRefOrGetter<HTMLElement | null | undefined>

Element to attach keydown listener to (defaults to control)

orientation

"horizontal" | "vertical" | "both" | undefined

Arrow key mapping. Ignored when `columns` is set (grid uses all 4 arrows).

circular

boolean | undefined

columns

MaybeRefOrGetter<number> | undefined

Column count for grid navigation. When set, items are treated as a 2D grid in row-major order: Left/Right step +/-1, Up/Down step +/-columns, Home/End go to row start/end, Ctrl+Home/End go to first/last overall.

onHighlight

((id: ID) => void) | undefined
Was this page helpful?

© 2016-1970 Vuetify, LLC
Ctrl+/