A field you type into to narrow a list, then choose from it.
Import it from rei-kit:
import { BaseCombobox } from 'rei-kit'
Open BaseCombobox in the live documentation, where every prop below is a control you can change, in four materials and ten palettes.
<script setup lang="ts">
import { ref } from 'vue'
import { BaseCombobox } from 'rei-kit'
const country = ref('')
const countries = [
{ value: 'tr', label: 'Türkiye' },
{ value: 'jp', label: 'Japan' },
{ value: 'de', label: 'Germany' },
]
/* More than one answer: the chosen ones become chips in the field. */
const recipients = ref<string[]>(['tr'])
/* A list that lives on a server. `@search` is the typed text, debounced by
the component; `filter="none"` stops it narrowing what the server already
narrowed. */
const results = ref<{ value: string; label: string }[]>([])
const loading = ref(false)
async function search(query: string) {
if (query === '') {
results.value = []
return
}
loading.value = true
const response = await fetch(`/api/cities?q=${encodeURIComponent(query)}`)
results.value = await response.json()
loading.value = false
}
</script>
<template>
<BaseCombobox
v-model="country"
label="Country"
:options="countries"
placeholder="Start typing"
empty-label="No country matches"
/>
<BaseCombobox
v-model="recipients"
mode="multiple"
label="Recipients"
:options="countries"
:remove-label="(name) => `Remove ${name}`"
placeholder="Add someone"
empty-label="Nobody matches"
/>
<BaseCombobox
label="City"
:options="results"
:loading="loading"
loading-label="Searching…"
filter="none"
placeholder="Type at least two letters"
empty-label="No city matches"
@search="search"
/>
</template>
| Prop | Type | Required or default | What it is for |
|---|---|---|---|
modelValue | Value | undefined | The chosen value, or values in `multiple` mode, with `v-model`. |
label | string | yes | |
options | readonly ComboboxOption<V>[] | yes | |
placeholder | string | '' | |
hint | string | '' | |
error | string | '' | |
disabled | boolean | false | |
emptyLabel | string | yes | Shown when nothing matches. Already translated. |
mode | M | 'single' as M | `multiple` keeps the chosen ones as chips in the field. |
removeLabel | ((label: string) => string) | undefined | Names a chip's remove button. Without it, chips carry no button. |
loading | boolean | false | Marks the list busy while an answer is on its way. |
loadingLabel | string | '' | Read while `loading`. Already translated. |
filter | 'local' | 'none' | 'local' | `none` when the server has already narrowed the list. |
debounce | number | 200 | How long typing settles before `search`, in milliseconds. |
virtualizeAfter | number | 150 | Past this many options, only the rows near the viewport are rendered. |
rowHeight | number | 36 | A row's height in pixels; the virtual window is measured in these. |
rei-kit is an accessible Vue 3 and Tailwind CSS 4 component library: 106 components, themed by role rather than by colour on three independent axes — ten WCAG-checked palettes, four materials and light or dark, each switched with one attribute. Typed, tested, SSR-safe, no runtime dependencies, MIT.