Combobox
A filterable single-select listbox — type to filter, navigate with the keyboard, pick one. It is the
first component driven end-to-end by @oriui/headless: the state machine + prop-getters own the
behavior (open/close, filtering, the active-descendant highlight, selection, and the full
WAI-ARIA keyboard), and OriCombobox renders the styled shell on top. Unlike the pure-CSS
components, the behavior needs JavaScript — the standalone classes give you the look, and
useCombobox gives you the behavior for any UI.
State is expressed on real elements: role="combobox" with aria-expanded / aria-controls /
aria-activedescendant on the input, role="listbox" + role="option" + aria-selected on the
popup. v-model holds the selected option's value.
Classes
The block is a labeled control wrapping a positioned listbox popup. The input reuses
.ori-input__field for its box, so the field look stays in one place.
| Class | Type | Description |
|---|---|---|
ori-combobox | Block | Required base class (wrapper div). |
ori-combobox_* (size) | Size | inherit · xs · sm · md · lg · xl · xxl field height. |
ori-color_* | Color | primary · … (focus ring + highlight + selected accent). |
ori-combobox__control · __input · __trigger · __clear · __listbox · __option · __empty | Part | control wrap / input / chevron / clear / popup / option / no-results. |
ori-combobox__label · __required · __hint · __error | Part | label / required-asterisk / helper / error. |
ori-combobox_fluid | Layout | full-width (stretches wrapper to 100 %). |
role=combobox · aria-expanded · aria-activedescendant · aria-selected · data-highlighted | State | real ARIA + data attributes, not classes. |
À la carte: the classes above ship in @oriui/css/components/combobox.css. The shared .ori-anchored
placement primitive is inlined here, so anchored.css needs no separate import. Import a foundation
(@oriui/css/base.css or @oriui/css/tokens.css) first — the token utilities (ori-color_*,
ori-size-radius_*, …) live there, not in the component file. The full bundle @oriui/css is the default and
already carries both; see à-la-carte imports.
Basic
Pass options ({ label, value, disabled? }[]) and bind v-model. Type to filter; the list opens
as you type.
- Apple
- Banana
- Cherry
- Grape
- Mango
Type to filter.
<script setup>
import { ref } from 'vue'
const fruit = ref(null)
const options = [
{ label: 'Apple', value: 'apple' },
{ label: 'Banana', value: 'banana' },
{ label: 'Cherry', value: 'cherry' },
{ label: 'Grape', value: 'grape' },
{ label: 'Mango', value: 'mango' }
]
</script>
<template>
<OriCombobox
v-model="fruit"
:options="options"
label="Fruit"
placeholder="Search a fruit…"
hint="Type to filter."
/>
</template>
Clearable
clearable shows a clear button while there is a selection. Keyboard users clear with Escape when
the listbox is closed (the button itself is a pointer affordance) — so the selection is always removable
without a mouse.
- Apple
- Banana
- Grape
<OriCombobox v-model="fruit" :options="options" label="Fruit" clearable placeholder="Pick one" />
Sizes
xs → xxl, like the other field controls.
- Apple
- Banana
- Apple
- Banana
<OriCombobox v-model="a" :options="options" label="Small" size="sm" />
<OriCombobox v-model="b" :options="options" label="Large" size="lg" />
States
disabled blocks the control; error renders a role="alert" message and flips aria-invalid;
required adds the asterisk and guards the selection — via aria-required plus custom validity, so
typing an unmatched query can't satisfy it and the form is blocked until a real option is chosen. A disabled option is skipped during keyboard
navigation and can't be selected.
- Apple
- Banana
- Apple
- Banana
Please choose a fruit.
<OriCombobox v-model="a" :options="options" label="Disabled" disabled />
<OriCombobox v-model="b" :options="options" label="With error" error="Please choose a fruit." required />
Headless (useCombobox)
The behavior ships separately in @oriui/headless, so you can build a fully custom combobox UI on
the same state machine + ARIA contract. useCombobox returns ready-to-v-bind prop bags and the
visible (filtered) items.
<script setup>
import { useCombobox } from '@oriui/headless/vue'
const { items, inputProps, listboxProps, getOptionProps, getOptionState } = useCombobox(() => ({
options: [
{ label: 'Apple', value: 'apple' },
{ label: 'Banana', value: 'banana' }
]
}))
</script>
<template>
<div>
<input v-bind="inputProps" />
<ul v-bind="listboxProps">
<li
v-for="(item, i) in items"
:key="item.value"
v-bind="getOptionProps(item, i)"
:data-selected="getOptionState(item).selected"
>
{{ item.label }}
</li>
</ul>
</div>
</template>
Options are a MaybeRefOrGetter — pass a getter (as above) or a ref instead of a plain object to react to a
changing option list or disabled.
Pass a filter ((item, query) => boolean) to override the default case-insensitive substring
match — for fuzzy matching, async results, or server-side filtering.
Accessibility
Implements the WAI-ARIA combobox with listbox popup pattern.
- The input is
role="combobox"witharia-autocomplete="list",aria-expanded, andaria-controlspointing at the listbox; the highlighted option is referenced byaria-activedescendant(focus stays on the input — no roving tabindex needed). - Options are
role="option"witharia-selected; the popup isrole="listbox"labeled by the field label. A disabled option carriesaria-disabledand is skipped by navigation. hint/errorare wired througharia-describedby(error supersedes hint); anaria-describedbyattribute you pass is joined with them.erroralso setsaria-invalid="true"androle="alert".requiredsetsaria-requiredand blocks submission until a value is selected.- The list dismisses on blur and on
Escape; selecting an option returns focus context to the input.
| Key | Action |
|---|---|
↓ / ↑ | Open the list and move the highlight (wraps). |
Home / End | Highlight the first / last option. |
Enter | Select the highlighted option. |
Escape | Close the list. |
Tab | Move focus away (closes the list). |
| Type | Filter the options. |
Framework API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
clearable | boolean | false | Show a clear button while there is a selection. |
color | ThemeColor | 'primary' | Accent for the focus ring, highlight, and selected option. |
disabled | boolean | false | Disable the control. |
error | string | — | Error message (role="alert"); sets aria-invalid="true". |
filter | (item, query) => boolean | substring | Override the default case-insensitive label filter. |
fluid | boolean | false | Full-width. |
form | string | — | Associate the hidden value input with a form by id. |
hint | string | — | Helper text; hidden while error is shown. |
id | string | — | Explicit base id; auto-generated via useId when omitted. |
invalid | boolean | false | Set aria-invalid without a message. |
label | string | — | Visible label, wired to the input via for / id. |
labels | Partial<ComboboxLabels> | English | Accessible names of the buttons: open and close (the trigger), clear. Pass only the ones you translate. |
name | string | — | Submit the selected value under this field name (hidden input). |
noResultsText | string | 'No results' | Shown when the filter matches nothing. |
options | ComboboxItem[] | required | { label, value, disabled? }[]. |
placeholder | string | — | Native placeholder. |
radius | RadiusSize | 'md' | Field + popup corner radius. |
required | boolean | false | Asterisk + aria-required; blocks submit until a value is selected. |
size | ActionSize | 'md' | Field height + text scale. |
v-model
v-model binds the selected option's value (string | null) via defineModel. Selecting an
option emits update:modelValue; clearing emits null.
Slots
| Slot | Scope | Description |
|---|---|---|
label | none | Rich label content (an icon + text, markup). Falls back to the label prop; keeps the for/id + listbox aria-labelledby wiring. |
option | { option, index, selected } | Per-option content — rich options such as an avatar + email or a leading icon. Rendered for every option. Falls back to option.label. |
empty | none | No-results content, shown when the filter matches nothing. Falls back to the noResultsText prop. |