Combobox
Filter a collection with text, move through matches, and accept one result.
Usage
Prefix
Filter items whose labels begin with the current query.
Filter items whose labels begin with the current query.
<!-- Combobox / Prefix -->
<script setup lang="ts">
import { ComboboxRoot, ComboboxInput, ComboboxContent, ComboboxItem, ComboboxEmpty } from '@sectile/vue/combobox'
const environments = [
{ id: 'production', label: 'Production', detail: 'customer.app' },
{ id: 'staging', label: 'Staging', detail: 'staging.customer.app' },
{ id: 'development', label: 'Development', detail: 'Local workspace' },
]
const environmentIDs = environments.map(({ id }) => id)
const environmentLabel = (id: string) => environments.find(item => item.id === id)?.label ?? id
</script>
<template>
<ComboboxRoot :items="environmentIDs" :text-value="environmentLabel" default-input-value="pro">
<ComboboxInput placeholder="Search environments" />
<ComboboxContent>
<ComboboxItem v-for="item in environments" :key="item.id" :value="item.id">
<strong>{{ item.label }}</strong>
<small>{{ item.detail }}</small>
</ComboboxItem>
<ComboboxEmpty>No matching environment</ComboboxEmpty>
</ComboboxContent>
</ComboboxRoot>
</template>Contains
Filter items whose labels contain the query instead of only matching its prefix.
Filter items whose labels contain the query instead of only matching its prefix.
<!-- Combobox / Contains -->
<script setup lang="ts">
import { ComboboxRoot, ComboboxInput, ComboboxContent, ComboboxItem, ComboboxEmpty } from '@sectile/vue/combobox'
const environments = [
{ id: 'production', label: 'Production', detail: 'customer.app' },
{ id: 'staging', label: 'Staging', detail: 'staging.customer.app' },
{ id: 'development', label: 'Development', detail: 'Local workspace' },
]
const environmentIDs = environments.map(({ id }) => id)
const environmentLabel = (id: string) => environments.find(item => item.id === id)?.label ?? id
</script>
<template>
<ComboboxRoot :items="environmentIDs" :text-value="environmentLabel" default-input-value="pro">
<ComboboxInput placeholder="Search environments" />
<ComboboxContent>
<ComboboxItem v-for="item in environments" :key="item.id" :value="item.id">
<strong>{{ item.label }}</strong>
<small>{{ item.detail }}</small>
</ComboboxItem>
<ComboboxEmpty>No matching environment</ComboboxEmpty>
</ComboboxContent>
</ComboboxRoot>
</template>Controlled
Let the parent own the current value and apply accepted changes back to the component.
Let the parent own the current value and apply accepted changes back to the component.
<!-- Combobox / Controlled -->
<script setup lang="ts">
import { ComboboxRoot, ComboboxInput, ComboboxContent, ComboboxItem, ComboboxEmpty } from '@sectile/vue/combobox'
const environments = [
{ id: 'production', label: 'Production', detail: 'customer.app' },
{ id: 'staging', label: 'Staging', detail: 'staging.customer.app' },
{ id: 'development', label: 'Development', detail: 'Local workspace' },
]
const environmentIDs = environments.map(({ id }) => id)
const environmentLabel = (id: string) => environments.find(item => item.id === id)?.label ?? id
</script>
<template>
<ComboboxRoot :items="environmentIDs" :text-value="environmentLabel" default-input-value="pro">
<ComboboxInput placeholder="Search environments" />
<ComboboxContent>
<ComboboxItem v-for="item in environments" :key="item.id" :value="item.id">
<strong>{{ item.label }}</strong>
<small>{{ item.detail }}</small>
</ComboboxItem>
<ComboboxEmpty>No matching environment</ComboboxEmpty>
</ComboboxContent>
</ComboboxRoot>
</template>Examples
IME
Keep composition text separate until an IME commit produces the accepted query.
Keep composition text separate until an IME commit produces the accepted query.
<!-- Combobox / Ime -->
<script setup lang="ts">
import { ComboboxRoot, ComboboxInput, ComboboxContent, ComboboxItem, ComboboxEmpty } from '@sectile/vue/combobox'
const environments = [
{ id: 'production', label: 'Production', detail: 'customer.app' },
{ id: 'staging', label: 'Staging', detail: 'staging.customer.app' },
{ id: 'development', label: 'Development', detail: 'Local workspace' },
]
const environmentIDs = environments.map(({ id }) => id)
const environmentLabel = (id: string) => environments.find(item => item.id === id)?.label ?? id
</script>
<template>
<ComboboxRoot :items="environmentIDs" :text-value="environmentLabel" default-input-value="pro">
<ComboboxInput placeholder="Search environments" />
<ComboboxContent>
<ComboboxItem v-for="item in environments" :key="item.id" :value="item.id">
<strong>{{ item.label }}</strong>
<small>{{ item.detail }}</small>
</ComboboxItem>
<ComboboxEmpty>No matching environment</ComboboxEmpty>
</ComboboxContent>
</ComboboxRoot>
</template>Floating positioning
This component uses the shared positioning engine. Use the live positioning example to change side, align, offsets, collision boundaries, strategy, and tracking while inspecting the resolved placement.
API
Vue package: @sectile/vue/combobox
ComboboxRootComboboxInputComboboxContentComboboxItemComboboxEmpty
Props
ComboboxRootProps
alignAlignment of positioned content relative to its anchor.
asElement or component rendered for this part.
asChildWhether to merge this part into its single child instead of rendering a wrapper.
avoidCollisionsWhether positioned content may flip or shift to remain visible.
collisionBoundaryBoundary used to keep positioned content visible.
collisionPaddingSpace kept between positioned content and its collision boundary.
defaultInputValueInitial uncontrolled text shown by the editable input.
defaultOpenInitial uncontrolled open state.
defaultValueInitial value used when the component owns its state.
disabledWhether interaction is unavailable.
hideWhenDetachedWhether positioned content hides when its anchor leaves the layout.
inputValueControlled text currently shown by the editable input.
itemsOrdered item values managed by the component.
labelAccessible name announced for the control.
modelValueCurrent value when state is controlled by the parent.
openWhether the associated popup or disclosure is open.
policiesBehavior policies that customize validation, movement, or selection.
positionWhether the popup is positioned relative to its trigger.
readonlyWhether the value can be inspected but not changed.
sidePreferred side of the anchor for positioned content.
sideOffsetDistance between positioned content and its anchor.
strategyCSS positioning strategy used for anchored content.
trackingUpdate strategy used while anchored content is open.
ComboboxItemProps
asElement or component rendered for this part.
asChildWhether to merge this part into its single child instead of rendering a wrapper.
disabledWhether interaction is unavailable.
valueCurrent value exposed by this contract.
ComboboxPartProps
asElement or component rendered for this part.
asChildWhether to merge this part into its single child instead of rendering a wrapper.
Slots
ComboboxRootSlotProps
disabledWhether interaction is unavailable.
highlightedValueValue currently highlighted for interaction.
inputValueCurrent editable input text.
openWhether the associated popup or disclosure is open.
readonlyWhether the value can be inspected but not changed.
valueCurrent value exposed by this contract.
ComboboxItemSlotProps
disabledWhether interaction is unavailable.
highlightedWhether this item is highlighted for interaction.
selectedWhether this item is selected.
valueCurrent value exposed by this contract.
Events
ComboboxRoot
acceptEmitted when the current draft becomes an accepted value.
highlightEmitted when the highlighted item changes.
update:inputValueEmitted when editable input text changes.
update:modelValueEmitted when the component requests a new controlled value.
update:openEmitted when the component requests a new open state.
Other types
ComboboxValueChangeHandler
type ComboboxValueChangeHandler = (value: string | null) => voidComboboxInputValueChangeHandler
type ComboboxInputValueChangeHandler = (value: string) => voidComboboxOpenChangeHandler
type ComboboxOpenChangeHandler = (value: boolean) => voidComboboxHighlightHandler
type ComboboxHighlightHandler = (value: string | null) => voidComboboxAcceptHandler
type ComboboxAcceptHandler = (value: string) => voidParts
Shared scope: [data-scope="combobox"]. Combine it with a part selector to keep styles local to this component.
| Part | Selector | Role | Extra attributes |
|---|---|---|---|
root | [data-part="root"] | Defines the component boundary and owns its composed parts. | — |
input | [data-part="input"] | Accepts the editable value or draft. | — |
content | [data-part="content"] | Contains the component content shown for the active state. | — |
item | [data-part="item"] | Represents one selectable or actionable item. | — |
empty | [data-part="empty"] | Shows feedback when no collection item matches. | — |
Keyboard interaction
| Key | Behavior |
|---|---|
| Arrow Down / Arrow Up | Open the popup and move the active option. |
| Enter | Accept the active option. |
| Escape | Close the popup without replacing the accepted value. |
| Text input | Filter options without breaking IME composition. |
Accessibility
The input exposes autocomplete, expanded state, popup ownership, and the active descendant while options expose selection.
See the corresponding WAI-ARIA pattern for the host accessibility contract.
