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

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

Properties

highlightedId

ShallowRef<ID | undefined>

Methods

highlight

(id: ID) => void

clear

() => void

next

() => void

prev

() => void

first

() => void

last

() => void

onKeydown

(e: KeyboardEvent) => void
Was this page helpful?

© 2016-1970 Vuetify, LLC
Ctrl+/