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.

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

API Reference

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

Ctrl+/