createCombobox
Low-level combobox coordinator for custom implementations. Most users should use the Combobox component instead.
Usage
import { createCombobox } from '@vuetify/v0'
const combobox = createCombobox({ strict: true })
// Register items with the underlying selection
combobox.selection.register({ id: 'apple', value: 'Apple' })
combobox.selection.register({ id: 'banana', value: 'Banana' })
combobox.selection.register({ id: 'cherry', value: 'Cherry' })
// Open the dropdown
combobox.open()
// Select an item — in single mode this updates the query and closes
combobox.select('banana')
// combobox.query.value === 'Banana'
// combobox.isOpen.value === false
// Filter is pristine after selection — all items still visible
// combobox.pristine.value === true
// Once user types, the filter activates
combobox.query.value = 'ch'
// combobox.pristine.value === false
// combobox.filtered.value === Set { 'cherry' }Context / DI
Context Object
createCombobox returns a ComboboxContext with the following API surface:
| Member | Type | Description |
|---|---|---|
open() | () => void | Opens the dropdown |
close() | () => void | Closes; applies strict revert if needed |
toggle() | () => void | Opens or closes |
select(id) | (id: ID) => void | Selects an item by ID |
clear() | () => void | Resets query and deselects all |
id | string | Base ID used for ARIA relationships |
inputId | string | ${id}-input |
listboxId | string | ${id}-listbox |
multiple | boolean | Resolved multiple flag |
strict | MaybeRefOrGetter<boolean> | Strict option ref |
disabled | MaybeRefOrGetter<boolean> | Disabled option ref |
name | string | undefined | Form field name |
form | string | undefined | Associated form ID |
Dependency Injection
Use createComboboxContext to get a DI-aware trinity for component-based setups:
import { createComboboxContext, useCombobox } from '@vuetify/v0'
// In a Root component
const [useMyCombobox, provideMyCombobox, context] = createComboboxContext({
namespace: 'my-combobox',
strict: true,
})
provideMyCombobox(context)
// In any child component
const combobox = useCombobox('my-combobox')useCombobox(namespace?) injects the nearest combobox context (default namespace: 'v0:combobox').
Adapters
Adapters extend ComboboxAdapter and translate a reactive query into a filtered ID set.
abstract class ComboboxAdapter {
abstract setup (context: ComboboxAdapterContext): ComboboxAdapterResult
}
interface ComboboxAdapterResult {
filtered: Ref<Set<ID>> // IDs that should be visible
isLoading: ShallowRef<boolean> // shows loading state in the UI
isEmpty: Ref<boolean> // true when no items match the query
}The context exposes query (the current search string), selection (the underlying selection context), and items (all registered IDs). Return the three refs above and the combobox wires them to the dropdown state automatically.
ClientComboboxAdapter
The default. Filters registered items locally using substring matching (case-insensitive). Pass custom filter options to override the matching logic:
import { ClientComboboxAdapter, createCombobox } from '@vuetify/v0'
const combobox = createCombobox({
adapter: new ClientComboboxAdapter({
filter: (query, value) => String(value).toLowerCase().startsWith(query.toLowerCase()),
}),
})ServerComboboxAdapter
A pass-through adapter that shows all registered items and sets isLoading to false. Use this when filtering is performed server-side — watch combobox.query to drive your own fetch:
import { ServerComboboxAdapter, createCombobox, useCombobox } from '@vuetify/v0'
import { watch } from 'vue'
const combobox = createCombobox({ adapter: new ServerComboboxAdapter() })
// In a component that injects the context:
const { query } = useCombobox()
watch(query, async q => {
const results = await fetch(`/api/search?q=${q}`).then(r => r.json())
// Update items via combobox.selection.register / unregister
})See the Combobox server example for a complete integration.
Architecture
createCombobox orchestrates four independent primitives without extending their chains — it composes them. The adapter translates queries into a filtered set; virtual focus uses that set to skip hidden items.
Options
interface ComboboxOptions {
multiple?: MaybeRefOrGetter<boolean> // Enable multi-select
mandatory?: MaybeRefOrGetter<boolean> // Prevent deselecting last item
disabled?: MaybeRefOrGetter<boolean> // Disable all interaction
strict?: MaybeRefOrGetter<boolean> // Revert query on close if no match
adapter?: ComboboxAdapter // Filtering strategy (default: ClientComboboxAdapter)
displayValue?: (value: unknown) => string // Format selected value for display in input
id?: string // Base ID for ARIA attributes
name?: string // Hidden input name for form submission
form?: string // Associated form ID
}Reactivity
| Property | Type | Reactive | Notes |
|---|---|---|---|
query | ShallowRef<string> | Current input text | |
pristine | ShallowRef<boolean> | true after selection; false once user types | |
filtered | Ref<Set<ID>> | IDs that pass the current filter | |
isEmpty | Ref<boolean> | true when filtered set is empty | |
isLoading | ShallowRef<boolean> | Forwarded from adapter | |
isOpen | ShallowRef<boolean> | Popover open state | |
selection | SelectionContext | — | Full selection API |
popover | PopoverReturn | — | Popover positioning API |
cursor | VirtualFocusReturn | — | Keyboard focus API |
inputEl | ShallowRef<HTMLElement | null> | Reference to the <input> element |
Examples
Selected country
None yet — start typing to search.
Recipes
Strict Mode
When strict: true, closing the dropdown without an active selection reverts the query:
If an item is selected,
queryresets to that item’s label.If nothing is selected,
queryresets to''.
Non-strict mode (default) leaves whatever text the user typed in place.
aria-autocomplete="both" is set automatically on the input when strict is enabled, per the WAI-ARIA combobox pattern.
Pristine Flag
pristine tracks whether the query reflects the current selection or is a live filter:
Starts as
true(no user input yet).Becomes
falsewhen the user types — the adapter receives the raw query.Resets to
trueafter a selection (select(id)), so reopening the dropdown always shows all items instead of the previous typed query.
// The adapter receives `search`, not `query` directly
const search = toRef(() => pristine.value ? '' : query.value)
const { filtered } = adapter.setup({ query: search, items })Multi-Select Behavior
In multiple mode, select(id) differs from single mode:
Toggles the item (select → deselect on second click) via
selection.toggle().Clears the query so the user can search for the next item.
Keeps the dropdown open.
Highlights the clicked item via
cursor.highlight(id)so ArrowDown continues from that position.Refocuses the input so keyboard navigation continues immediately.
FAQ
createCombobox is the low-level coordinator for fully custom markup and ARIA wiring. Most apps should use the Combobox component, which wraps it with sensible defaults — only drop to the composable when you need full control over the rendered structure.
pristine is true when the query reflects the current selection and false once the user types. It resets to true after select(id), so reopening the dropdown shows all items instead of the previous typed query.
Pass new ServerComboboxAdapter() and watch combobox.query to drive your own fetch, registering or unregistering items through combobox.selection. The default ClientComboboxAdapter does case-insensitive substring matching in memory.
With strict: true, closing the dropdown without a live match reverts query — to the selected item’s label if one is selected, otherwise to ''. Non-strict (the default) leaves whatever the user typed. It also sets aria-autocomplete="both" per the WAI-ARIA pattern.
It toggles the item, clears the query so the user can search for the next one, keeps the dropdown open, highlights the clicked item via cursor.highlight(id), and refocuses the input — so keyboard navigation continues from that position.
cursor is the useVirtualFocus surface — it tracks the keyboard-highlighted option through aria-activedescendant while real focus stays in the input. Arrow keys call cursor.next() / cursor.prev(), and Enter reads cursor.highlightedId to route to select(id).