Combobox
A headless autocomplete combobox with client and server-side filtering support. Uses createSelection for state management, useVirtualFocus for keyboard navigation, and usePopover for native popover positioning. Unlike Select, the Combobox renders a real <input> — the query drives filtering and the selection stores the chosen value.
Usage
The Combobox component follows the same compound pattern as Select, but replaces the activator button with a Control input that accepts free text. Filtering happens automatically as the user types.
Selected: None
<script setup lang="ts">
import { Combobox } from '@vuetify/v0'
import { shallowRef } from 'vue'
const selected = shallowRef<string>()
const fruits = [
{ id: 'apple', label: 'Apple' },
{ id: 'banana', label: 'Banana' },
{ id: 'cherry', label: 'Cherry' },
{ id: 'date', label: 'Date' },
{ id: 'elderberry', label: 'Elderberry' },
{ id: 'fig', label: 'Fig' },
{ id: 'grape', label: 'Grape' },
]
</script>
<template>
<div class="flex flex-col gap-4 max-w-xs mx-auto">
<Combobox.Root v-model="selected">
<Combobox.Activator class="flex items-center gap-1 w-full px-3 py-2 rounded-lg border border-divider bg-surface text-on-surface text-sm">
<Combobox.Control
class="flex-1 bg-transparent outline-none text-on-surface placeholder:text-on-surface-variant"
placeholder="Search fruits…"
/>
<Combobox.Cue class="opacity-50 cursor-pointer transition-transform data-[state=open]:rotate-180"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M5 9l7 7 7-7" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" /></svg></Combobox.Cue>
</Combobox.Activator>
<Combobox.Content class="p-1 rounded-lg border border-divider bg-surface shadow-lg" :style="{ minWidth: 'anchor-size(width)' }">
<Combobox.Item
v-for="item in fruits"
:id="item.id"
:key="item.id"
class="px-3 py-2 rounded-md cursor-default select-none text-sm text-on-surface data-[selected]:text-primary data-[selected]:font-medium data-[highlighted]:bg-primary data-[highlighted]:text-on-primary data-[highlighted]:data-[selected]:text-on-primary"
:value="item.label"
>
{{ item.label }}
</Combobox.Item>
<Combobox.Empty v-slot="{ query }" class="px-3 py-2 text-sm text-on-surface-variant">
No results for "{{ query }}"
</Combobox.Empty>
</Combobox.Content>
</Combobox.Root>
<p class="text-sm text-on-surface-variant">
Selected: {{ selected ?? 'None' }}
</p>
</div>
</template>
Anatomy
<script setup lang="ts">
import { Combobox } from '@vuetify/v0'
</script>
<template>
<Combobox.Root>
<Combobox.Activator>
<Combobox.Control />
<Combobox.Cue />
</Combobox.Activator>
<Combobox.Description />
<Combobox.Error />
<Combobox.Content>
<Combobox.Item />
<Combobox.Empty />
</Combobox.Content>
<Combobox.HiddenInput />
</Combobox.Root>
</template>Architecture
Root creates selection, virtual focus, popover, and adapter contexts. Control drives the query string — the adapter translates queries into a filtered ID set. Items register with selection and use v-show (not v-if) against the filtered set, preserving selection state even when hidden. Empty renders when the filtered set is empty. Description and Error provide accessible help text and validation messages linked to Control via ARIA attributes.
Examples
Multi-Select
Set multiple on Root to enable multi-selection. The dropdown stays open after each selection and the query clears so the user can keep searching. v-model binds to an array of IDs. Render selected chips or tags using the selected model value directly.
Selected: None
Strict Mode
Set strict on Root to enforce valid selections. When the dropdown closes without a matching item being selected, the input reverts to the last confirmed selection (or clears if nothing was selected). Use this when free-text values are not allowed.
Strict mode: typing free text is allowed, but closing the dropdown without a match reverts the input to the last valid selection.
Selected: None
Disabled States
Both individual items and the entire combobox can be disabled. Disabled items are skipped by virtual focus keyboard navigation. The disabled prop on Root prevents the input from opening the dropdown and suppresses keyboard interactions.
Selected: None
Server-Side Filtering
Pass a ComboboxServerAdapter instance via the adapter prop to disable client-side filtering. The adapter is a pass-through — it shows all registered items and leaves filtering to the consumer. Watch context.query via useComboboxContext() to drive your own async data fetching.
Selected: None
Accessibility
The Combobox implements the WAI-ARIA Combobox↗ pattern with a listbox popup.
ARIA Attributes
| Attribute | Value | Component |
|---|---|---|
role | combobox | Control |
role | listbox | Content |
role | option | Item |
aria-autocomplete | list / both | Control |
aria-expanded | true / false | Control |
aria-haspopup | listbox | Control |
aria-controls | listbox ID | Control |
aria-activedescendant | highlighted option ID | Control |
aria-describedby | description ID | Control (when Description mounted) |
aria-errormessage | error ID | Control (when Error mounted and errors exist) |
aria-invalid | true | Control (when invalid) |
aria-selected | true / false | Item |
aria-disabled | true | Item (when disabled) |
aria-multiselectable | true | Content (when multiple) |
aria-hidden | true | Cue |
aria-live | polite | Error |
aria-autocomplete="both" is set automatically when strict is enabled, signaling that the input value will revert to a valid option on close.
Keyboard Navigation
| Key | Action |
|---|---|
ArrowDown / ArrowUp | Open dropdown, or move highlight down / up |
Enter | Select highlighted item |
Escape | Close dropdown |
Tab | Close dropdown and move focus |
Home | Move highlight to first item |
End | Move highlight to last item |
Combobox.Root
Props
mandatory
booleanControls mandatory selection behavior: - false (default): No mandatory selection enforcement - true: Prevents deselecting the last selected item
Default: false
error
booleanManual error state override — forces invalid regardless of error messages
Default: false
displayValue
(value: unknown) => stringMaps selected value to input display text. Defaults to String(value).
modelValue
anyEvents
update:model-value
[value: any]Slots
default
ComboboxRootSlotPropsCombobox.Activator
Props
Slots
default
ComboboxActivatorSlotPropsCombobox.Content
Props
Slots
default
ComboboxContentSlotPropsCombobox.Control
Props
Slots
default
ComboboxControlSlotPropsCombobox.Cue
Props
Slots
default
ComboboxCueSlotPropsCombobox.Description
Props
Slots
default
ComboboxDescriptionSlotPropsCombobox.Empty
Props
Slots
default
ComboboxEmptySlotPropsCombobox.Error
Props
Slots
default
ComboboxErrorSlotPropsCombobox.HiddenInput
Props
Combobox.Item
Props
Slots
default
ComboboxItemSlotProps<V>Recipes
Form Submission
Set name on Root to auto-render hidden inputs for form submission — one per selected value in multi-select mode:
<template>
<Combobox.Root v-model="value" name="country">
<!-- ... -->
</Combobox.Root>
</template>Custom Client Filtering
Pass a ComboboxClientAdapter with a custom filter function to override the default substring matching:
<script setup lang="ts">
import { Combobox, ComboboxClientAdapter } from '@vuetify/v0'
const adapter = new ComboboxClientAdapter({
filter: (query, value) => String(value).toLowerCase().startsWith(query.toLowerCase()),
})
</script>
<template>
<Combobox.Root :adapter>
<!-- ... -->
</Combobox.Root>
</template>Open on Input Only
By default the dropdown opens on focus. Set open-on="input" on Control to only open when the user starts typing — useful for server search where an empty query should not trigger a fetch:
<template>
<Combobox.Control open-on="input" placeholder="Type to search…" />
</template>Data Attributes
Style interactive states without slot props:
| Attribute | Values | Component |
|---|---|---|
data-selected | true | Item |
data-highlighted | "" | Item |
data-disabled | true | Item |
data-state | "open" / "closed" | Activator, Cue |