createCombobox
Low-level combobox coordinator for custom implementations. Most users should use the Combobox component instead.
Usage
Context / DI
Context Object
createCombobox returns a ComboboxContext with the following API surface:
| Member | Type | Description |
|---|---|---|
open() | () => void | Opens the dropdown |
close() | () => void | Closes and resets query/pristine (always discards uncommitted text; does not commit) |
toggle() | () => void | Opens or closes |
commit() | () => void | Commits typed text (exact browse match, or mint when !strict); empty, pristine, or strict-unmatched just close |
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 | Readonly<Ref<boolean>> | Multiple-select 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, items }, where items is the registered ticket list (ad-hoc free-text tickets excluded). 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.
Dropdown placement is a separate seam: pass positionAdapter (a PopoverAdapter) rather than reusing adapter. Default is CSS anchor positioning; swap in FloatingUIPopoverAdapter from @vuetify/v0/popover/adapters/floating-ui the same way as usePopover.
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> // Constrain accepted values to registered options; unmatched text is discarded on confirm (Enter/Tab/click-outside/blur)
adapter?: ComboboxAdapter // Filtering strategy (default: ClientComboboxAdapter)
positionAdapter?: PopoverAdapter // Dropdown positioning engine (default: V0PopoverAdapter)
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
Recipes
Strict Mode
strict constrains accepted values to registered options. Confirming with Enter, Tab, click-outside, or blur:
Non-strict (default):
commit()mints a ticket for unmatched text, selects it, and emits it.Strict: unmatched text is discarded; the dropdown closes.
close() and Escape always discard uncommitted text — they never commit. After close, pristine is true so display falls back to the selected ticket’s label (or '' if nothing is selected).
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
trueafterselect(id)orcommit(), andqueryis cleared, so the adapter sees an empty query and unfilters. Reopening the dropdown always shows all 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.
Virtualized listbox
Keep every ticket registered. Window the DOM with createVirtual. Write ticket.el from the row, then scrollTo before cursor.highlight so off-screen arrows land on a real node.
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, confirming unmatched text (Enter, Tab, click-outside, or blur) discards it and closes; only registered options can be chosen. Non-strict (the default) commits the typed value as a new selection. close() and Escape always discard uncommitted text. aria-autocomplete is set by Combobox.Control, not this composable.
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).