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

Combobox

A headless autocomplete input that filters options as the user types, with client and server-side filtering support.

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

vue
<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.

Combobox Architecture

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

Combobox Architecture

Examples

Async assignee picker

A GitHub-style assignee picker that searches people from a mocked server as you type. It wires ServerComboboxAdapter onto Combobox.Root to disable client-side filtering, debounces the query, runs an async lookup that resolves a Promise, shows a loading hint while the request is in flight, and renders multi-select chips for everyone assigned. When the server returns nothing, Combobox.Empty reports the unmatched term.

The interesting piece is how the query reaches the fetch without prop-drilling. A renderless SearchWatcher — a defineComponent with render: () => null — reads the combobox context via useComboboxContext and watches its query, emitting a search event that the composable debounces before calling the mock search(). Results flow back down as a prop, so Combobox.Item re-registers on each update. open-on="input" on Combobox.Control keeps an empty focus from firing a needless fetch, and the name prop on Combobox.Root auto-renders the hidden inputs for native form submission — one per selected handle — so you never place a hidden input by hand (Combobox does not export one).

Reach for this whenever the dataset is too large to ship to the client, needs full-text indexing, or depends on server context like permissions or org membership. The trade-off versus the default ClientComboboxAdapter is the debounce-and-network latency and the loading state you must surface; for small static lists, prefer client filtering. Related: createSelection powers the multi-select state, and Select covers the non-typeahead equivalent.

FileRole
useUserSearch.tsOwns the mock user dataset, debounced onSearch, async search returning a Promise, loading flag, and assignee state
UserPicker.vueReusable combobox surface — server adapter, SearchWatcher, chips, results list, and Empty state; owns the UnoCSS classes
user-picker.vueDemo entry wiring the composable to the picker, with an assignment summary and a clear action
Results are fetched from a mock server as you type.
No one assigned yet

Recipes

Form Submission

Set name on Root to auto-render hidden inputs for form submission — one per selected value in multi-select mode:

vue
<template>
  <Combobox.Root v-model="value" name="country">
    <!-- ... -->
  </Combobox.Root>
</template>

Custom Client Filtering

Pass a ClientComboboxAdapter with a custom filter function to override the default substring matching:

vue
<script setup lang="ts">
  import { Combobox, ClientComboboxAdapter } from '@vuetify/v0'

  const adapter = new ClientComboboxAdapter({
    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:

vue
<template>
  <Combobox.Control open-on="input" placeholder="Type to search…" />
</template>

Data Attributes

Style interactive states without slot props:

AttributeValuesComponent
data-selectedtrueItem
data-highlighted""Item
data-disabledtrueItem
data-state"open" / "closed"Activator, Cue

Accessibility

The Combobox implements the WAI-ARIA Combobox↗︎ pattern with a listbox popup.

ARIA Attributes

AttributeValueComponent
rolecomboboxControl
rolelistboxContent
roleoptionItem
aria-autocompletelist / bothControl
aria-expandedtrue / falseControl
aria-haspopuplistboxControl
aria-controlslistbox IDControl
aria-activedescendanthighlighted option IDControl
aria-describedbydescription IDControl (when Description mounted)
aria-errormessageerror IDControl (when Error mounted and errors exist)
aria-invalidtrueControl (when invalid)
aria-selectedtrue / falseItem
aria-disabledtrueItem (when disabled)
aria-multiselectabletrueContent (when multiple)
aria-hiddentrueCue
aria-livepoliteError
Tip

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

KeyAction
ArrowDown / ArrowUpOpen dropdown, or move highlight down / up
EnterSelect highlighted item
EscapeClose dropdown
TabClose dropdown and move focus
HomeMove highlight to first item
EndMove highlight to last item

FAQ

Discord
Need help? Join our community for support and discussions ↗
Was this page helpful?

Ctrl+/