ComboBox
A combo box is a text field with a list behind it: typing narrows the options, and the reader either picks one or — if you allow it — keeps what they typed. Where the reader should only ever choose from the list and never type, a Select is simpler and says so.
import {
ComboBox,
ComboBoxInputGroup,
ComboBoxPopover,
ComboBoxTrigger,
Input,
ListBox,
ListBoxItem,
} from "ropav";items is required, and the default slot receives the filtered list — so the options are rendered from what the filter narrowed to, not from the whole set. ComboBoxInputGroup is the field, ComboBoxTrigger the button that opens the popover without typing, and the popover holds an ordinary ListBox.
combo-box-filtering.vue
<script setup lang="ts">
import {
ComboBox,
ComboBoxInputGroup,
ComboBoxPopover,
ComboBoxTrigger,
Input,
Label,
ListBox,
ListBoxItem,
ListBoxItemIndicator,
} from "ropav";
const cities = [
{ id: "lisbon", name: "Lisbon" },
{ id: "osaka", name: "Osaka" },
{ id: "quito", name: "Quito" },
{ id: "tallinn", name: "Tallinn" },
];
const byName = (city: { name: string }) => city.name;
</script>
<template>
<ComboBox v-slot="{ items: matches }" class="field" :item-text-value="byName" :items="cities">
<Label>Destination</Label>
<ComboBoxInputGroup>
<Input placeholder="Start typing…" />
<ComboBoxTrigger />
</ComboBoxInputGroup>
<ComboBoxPopover>
<ListBox>
<ListBoxItem v-for="city in matches" :id="city.id" :key="city.id" :text-value="city.name">
{{ city.name }}
<ListBoxItemIndicator />
</ListBoxItem>
</ListBox>
</ComboBoxPopover>
</ComboBox>
</template>
<style scoped>
.field {
width: calc(var(--rp-spacing) * 64);
}
</style>Filtering and opening
default-filter replaces the built-in match; pass null to switch filtering off entirely, which is what you do when the server is already returning the matches. item-text-value is both what is shown in the field and what the filter reads.
menu-trigger decides when the popover appears — on focus by default, or only once the reader has typed. allows-empty-collection lets it open with nothing in it, for a "no matches" message.
Text that is not an option
allows-custom-value lets what the reader typed stand as the value. With it off, leaving the field snaps back to the last real option, which should-close-on-blur controls.
form-value decides what actually gets submitted — the chosen option's key, or the text in the field. On a combo box that allows custom values the second is usually what you want, since there may be no key at all.
Accessibility
- The field is a
role="combobox"carryingaria-autocomplete="list"andaria-expanded, so a reader is told there are options to narrow before they start typing. - Filtering changes what is under the reader while they type. The count is announced as the list narrows, which is what stops a shrinking list from being silent.
item-text-valueis what typeahead and the filter both read. Without it, an option whose label is markup rather than text cannot be matched at all.allows-custom-valueis a real accessibility decision, not only a data one: with it off, a reader who types something almost-right loses it on blur with no warning.
API
ComboBox
| Prop | Type | Default | Description |
|---|---|---|---|
allowsCustomValue | boolean | — | Whether text matching no option may stand as the value. |
allowsEmptyCollection | boolean | — | Whether the popover may open with no options in it. |
ariaDescribedby | string | — | |
ariaLabel | string | — | |
ariaLabelledby | string | — | |
autoComplete | string | — | |
autoFocus | boolean | — | |
defaultFilter | ComboBoxFilter | null | — | Whether an option's text matches what has been typed. Absent means a locale-aware "contains", which is what the React build defaults to. An explicit null means the caller narrows items itself — an asynchronous search, or a list already sliced — and nothing filters a second time. |
defaultInputValue | string | — | Text the field starts with. Defaults to the chosen option's own text. |
defaultValue | SelectedValue | — | The key(s) the combo box starts on. |
disabledBehavior | "all" | "selection" | "all" | Whether a disabled key is also unfocusable. |
disabledKeys | Iterable<CollectionKey> | — | Keys that cannot be chosen. |
form | string | — | Id of the <form> the field belongs to. |
formValue | "key" | "text" | "key"
Forced to `"text"` by `allowsCustomValue`, where text matching no option is the whole point
and there is no key to send. | Whether the form carries the chosen key or the text in the field. |
fullWidth | boolean | — | Whether the combo box stretches to fill its container. |
id | string | — | Id of the field itself, which the label points for at. |
inputMode | string | — | |
inputValue | string | — | Text in the field. Controlled. |
isDisabled | boolean | — | |
isInvalid | boolean | — | Controlled validity. Present at all — true or false — shadows validate. |
isReadOnly | boolean | — | |
isRequired | boolean | — | |
itemDisabled | ((item: T) => boolean) | — | Whether an item cannot be chosen. Merges with disabledKeys. |
itemKey | ((item: T, index: number) => CollectionKey) | — | An item's key. Defaults to its own id, then key, then its index. |
items* | readonly T[] | — | The options, as data. **Required.** The React build reads its options out of a hidden render pass of its children. Rendering is what creates DOM in vapor, so there is no such pass — and the options only exist in the DOM while the popover is open. Everything a closed combo box has to answer for reads this instead: the text the field shows for the chosen option, the list the filter runs over, and the key the form submits. Required rather than optional so leaving it out is a type error instead of a combo box that quietly shows an empty field over a value it holds. |
itemTextValue | ((item: T) => string | undefined) | — | Text shown for an item in the field, and matched on by the filter. Defaults to its text. |
maxLength | number | — | |
menuTrigger | "focus" | "input" | "manual" | "focus" | What has to happen for the popover to appear. |
minLength | number | — | |
name | string | — | Name the value is submitted under. |
pattern | string | — | |
placeholder | string | — | |
selectionMode | "single" | "multiple" | "single" | Whether one or several options can be chosen. |
shouldCloseOnBlur | boolean | true | Whether leaving the field commits what is in it and closes the popover. |
shouldFocusWrap | boolean | — | Whether the arrow keys wrap around the ends of the list. |
size | "xs" | "sm" | "md" | "lg" | "md" | How tall the field stands, matching a button of the same size. Handed to the Input inside and to the listbox in the popover, unless either declares one of its own. |
validate | ValidationFunction<ComboBoxValidationValue> | — | |
validationBehavior | "aria" | "native" | "native" | |
value | SelectedValue | — | The chosen key, or keys when multiple. Controlled. |
variant | "primary" | "secondary" | "primary" | Visual variant the Input inside picks up. |
Slots: default.
| Event | Payload |
|---|---|
change | SelectedValue |
focusChange | boolean |
inputChange | string |
openChange | boolean, "focus" | "input" | "manual" | undefined |
update:inputValue | string |
update:value | SelectedValue |
ComboBoxInputGroup
The default slot receives:
| Slot prop | Type |
|---|---|
isDisabled | boolean |
isFocusVisible | boolean |
isFocusWithin | boolean |
isHovered | boolean |
isInvalid | boolean |
ComboBoxPopover
| Prop | Type | Default | Description |
|---|---|---|---|
arrowBoundaryOffset | number | — | |
containerPadding | number | — | |
crossOffset | number | — | Shift along the field's edge, in pixels. |
isKeyboardDismissDisabled | boolean | — | |
offset | number | 8 | Distance from the field, in pixels. |
placement | "bottom" | "bottom end" | "bottom left" | "bottom right" | "bottom start" | "end" | "end bottom" | "end top" | "left" | "left bottom" | "left top" | "right" | "right bottom" | "right top" | "start" | "start bottom" | "start top" | "top" | "top end" | "top left" | "top right" | "top start" | "bottom" | |
shouldFlip | boolean | — | Whether the popover may flip to the other side when it does not fit. |
Slots: default.
ComboBoxTrigger
Slots: default.
ComboBoxValue
| Prop | Type | Default | Description |
|---|---|---|---|
placeholder | string | — | Shown when nothing is chosen. |
Slots: default.