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.
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.
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.
File
Role
useUserSearch.ts
Owns the mock user dataset, debounced onSearch, async search returning a Promise, loading flag, and assignee state
UserPicker.vue
Reusable combobox surface — server adapter, SearchWatcher, chips, results list, and Empty state; owns the UnoCSS classes
user-picker.vue
Demo 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.
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>
Combobox accepts free text and filters options as the user types. Select is the non-typeahead equivalent, where the user picks from a fixed list without typing.
Pass a ServerComboboxAdapter to Combobox.Root to disable client-side filtering, then debounce the query and run your async lookup. The default ClientComboboxAdapter filters the in-memory list by substring.
Items render with v-show (not v-if) against the filtered set, so they’re hidden rather than unmounted. Selection state is preserved even when an option isn’t currently matched.
Set strict on Combobox.Root. When the dropdown closes, the input reverts to the selected option’s value — or clears if nothing is selected — so an unmatched query never sticks.
Set name on Combobox.Root and it auto-renders one hidden input per selected value. Combobox doesn’t export a hidden-input sub-component, so you never place one by hand.
Discord
Need help? Join our community for support and discussions ↗