combobox
A combobox is a text field with a list behind it. Typing narrows the list, the arrow keys
walk it, and Enter takes the option under the cursor. It is what a <select> becomes once
there are more options than a reader will scroll through.
<Combobox
label="Language"
items={[
{ value: "ts", label: "TypeScript" },
{ value: "rs", label: "Rust" },
]}
/>
<Combobox
label="Language"
:items="[
{ value: 'ts', label: 'TypeScript' },
{ value: 'rs', label: 'Rust' },
]"
/>
Options are data, not markup
items is an array of plain objects, and every one of them carries a label the reader
sees and a value the form submits. Keeping the two apart is the whole reason a combobox
is not a text input: what is stored need not read like English, and what reads like
English need not be stable.
An option can also carry disabled and an icon. The text stays plain strings so an item
stays serializable; for richer markup use the adapter’s escape hatch, which in Vue is the
item scoped slot.
The filter is the component’s own
The field owns the text and the query. There is no inputValue prop, no filter prop and
no collection to build: pass items and the component narrows them, case-insensitively,
on any substring of the label.
Only typing narrows the list. Ark rewrites the field itself when an option is picked or the field is cleared, and treating that rewrite as a query would leave the list showing the one option already chosen, so every other reason widens the list back to everything. A reader who reopens the list sees all of it.
Filtering server-side is the same component with a different items: fetch on your own
input handler and hand back a new array. Because the collection is rebuilt from the prop
rather than captured once, the new options reach the popup on the next render.
One selection or several
The value is an array of option values whatever the mode, because a multiple selection is many and a single one is one, and one shape beats converting between two.
React takes value with onValueChange, or defaultValue to leave the state alone. Vue
takes v-model, with defaultValue as the uncontrolled counterpart.
const [picked, setPicked] = useState<string[]>([]);
<Combobox items={items} multiple value={picked} onValueChange={(d) => setPicked(d.value)} />;
<script setup lang="ts">
import { Combobox } from "@75neo/vue";
const picked = ref<string[]>([]);
</script>
<template>
<Combobox v-model="picked" :items="items" multiple />
</template>
With multiple the field clears itself after each pick, which is Ark’s behaviour and the
right one: the next thing typed is a new query rather than an edit of the last answer.
Render the chosen options somewhere of your own.
The parts you can turn off
clearabledraws the button that empties the field, and is the one that is on by default.openOnClickshows the whole list on the first click, rather than waiting for a keystroke. Worth it for a short list, less so for a long one.allowCustomValueaccepts text matching no option, for a field that suggests rather than restricts.emptyMessagereplaces “No results found.” when the filter matches nothing.
Where the popup lives
The list is rendered at the end of the document rather than where the component is written, so a card, a dialog or a toolbar that clips its overflow cannot cut the list off. The move is delayed until the component mounts, which keeps the server-rendered markup and the first client render identical.
What that costs is stacking: the list is a sibling of everything else at the end of the
document, so a fixed header with a higher z-index will still cover it. The positioner
slot carries the stacking context, and ui.positioner is where to raise it.
Forms
The component renders a hidden input, so a combobox inside a form submits like any other
field. name names it and the submitted value is the option’s value. required and
invalid do what they do on any input, and readOnly shows a selection without letting
anyone change it, which is different from disabled: a read-only field still takes focus.
Slots
The same word names the recipe slot, the data-slot attribute and the key in ui.
- base
- label
- control
- input
- trigger
- clearTrigger
- positioner
- content
- list
- item
- leadingIcon
- itemText
- itemIndicator
- empty
Variants
Read off the recipe, so these are the values that actually resolve. A Theme layer can change which one is the default; it cannot add a value.
- color
- primarysecondarysuccessinfowarningerrorneutral
- size
- smmdlg
Props
Taken by both adapters, read out of the component module. The same names and the same types work in React and Vue.
- ui
ComboboxUI
Per-slot class overrides.
- color
"primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral"
Defaults to
"primary"- size
"sm" | "md" | "lg"
Defaults to
"md"- itemsrequired
ComboboxItem<ReactNode>[]
The options to offer, before filtering.
- label
string
Caption above the field.
- placeholder
string
Placeholder for the empty field.
- emptyMessage
string
Shown in place of the list when nothing matches.
Defaults to
"No results found."- multiple
boolean
Allow more than one option to be selected.
- clearable
boolean
Show the button that empties the field.
Defaults to
true- openOnClick
boolean
Open the list when the field is clicked, rather than only when typing.
- allowCustomValue
boolean
Accept text that matches no option.
- disabled
boolean
- readOnly
boolean
- invalid
boolean
- required
boolean
- name
string
Submits the selection under this name inside a form.
- trailingIcon
ReactNode
Replaces the chevron that opens the list.
- clearIcon
ReactNode
Replaces the cross that empties the field.
- selectedIcon
ReactNode
Replaces the tick beside a selected option.
React only
Also takes Omit<React.HTMLAttributes<HTMLDivElement>, "color" | "defaultValue" | "dir" | "onSelect">, and Pick< ComboboxRootProps<Item>, "value" | "defaultValue" | "onValueChange" | "onOpenChange" | "ids" >. A className among them reaches the base slot.