The Combobox component is a select box you can type in. It filters as you type, holds one value or many, groups its options, and can fetch them from your server. Use the plain Select when the list is short and needs no search.
Example
Pass the options as items and bind a value. Plain strings are the quickest way to start.
value: Bangladesh
<script>
import { Combobox } from "theui-svelte";
const countries = ["Bangladesh", "Brazil", "Canada", "Denmark"]
let country = $state("Bangladesh")
</script>
<Combobox items={countries} bind:value={country} placeholder="Pick a country">
Country
</Combobox>Option Objects
An option can also be an object with a value, the text to show, a disabled flag and a group name. The value is what you get back; the text is what the user reads and searches.
<script>
const roles = [
{ value: "admin", text: "Administrator", group: "Staff" },
{ value: "editor", text: "Editor", group: "Staff" },
{ value: "member", text: "Member", group: "Public" },
{ value: "guest", text: "Guest", group: "Public", disabled: true },
]
</script>
<Combobox items={roles} placeholder="Choose a role">Role</Combobox>Multiple Selection
Add multiple and the value becomes an array. Each choice is shown as a chip with its own remove button, and Backspace in an empty field removes the last one.
value: ["Svelte"]
<script>
let frameworks = $state(["Svelte"])
</script>
<Combobox
items={["Svelte", "React", "Vue", "Solid"]}
bind:value={frameworks}
multiple
placeholder="Pick a few"
>Frameworks</Combobox>Creating Options
With creatable, anything typed that does not match an option can be added as a new one. Use createText to reword the entry that offers it.
value: []
<Combobox
items={["Design", "Engineering", "Marketing"]}
bind:value={tags}
multiple
creatable
createText={(q) => `Add "${q}"`}
>Tags</Combobox>Async Search
Pass onsearch and the component stops filtering on its own: it hands you what was typed and shows whatever items you set. Use loading while the request is on its way.
<script>
let items = $state([])
let loading = $state(false)
const search = async (query) => {
if (!query.trim()) { items = []; return }
loading = true
items = await fetchCountries(query)
loading = false
}
</script>
<Combobox {items} {loading} onsearch={search} placeholder="Search..." emptyText="Nothing found">
Country
</Combobox>Custom Options
The option snippet takes over how each row is drawn, and is handed the option itself, so a row can hold an avatar, a description or a badge.
<Combobox items={roles} placeholder="Choose a role">
Rich options
{#snippet option(item)}
<Avatar size="xs" name={item.text} />
<span class="grow">{item.text}</span>
<span class="text-xs text-muted">{item.group}</span>
{/snippet}
</Combobox>Without Search
Set searchable=false to keep the panel and the keyboard but stop the typing, which turns the component into a styled select. clearable=false hides the clear button.
<Combobox items={countries} value="Canada" searchable={false}>Not searchable</Combobox>
<Combobox items={countries} value="Japan" clearable={false}>Cannot be cleared</Combobox>
<Combobox items={countries} value="Kenya" disabled>Disabled</Combobox>In a Form
Give the component a name to submit the value. A multiple combobox submits one field for each choice, the way a multiple select does.
<form method="POST">
<Combobox items={countries} name="country" value="Brazil">Country</Combobox>
<button>Save</button>
</form>Animation Speed
The animationSpeed prop sets how quickly the field and its panel react to focus, hover and opening. It takes "none", "slower", "slow", "normal", "fast" and "faster", and the default is "normal". Inside a Form or a Fieldset the value is inherited, so one setting covers every field at once.
<Combobox items={countries} animationSpeed="slower" placeholder="Slower" />
<Combobox items={countries} animationSpeed="normal" placeholder="Normal" />
<Combobox items={countries} animationSpeed="faster" placeholder="Faster" />
<Combobox items={countries} animationSpeed="none" placeholder="No animation" />Reset Styles
Set reset to true to drop the border, background and focus ring of the field and keep only the classes you pass. The chips and the panel keep their layout, so the component still works the same way. The default is false, and a Form or a Fieldset can set it for every field it holds.
<Combobox
items={countries}
reset
fieldClasses="w-full border-b-2 border-gray-400 px-1 py-2"
placeholder="Underline only" />Customization
Sizes and variants come from the Form or the Fieldset, or from the props here. Use fieldClasses for the box, panelClass for the list, optionClass for each row and chipClasses for the chips.
<Combobox items={countries} size="lg" variant="flat" placeholder="Flat and large" />
<Combobox items={countries} rounded="full" placeholder="Fully rounded" />
<Combobox items={countries} multiple chipClasses="bg-brand-500 text-on-brand" />Keyboard
The down arrow opens the list and moves through it, the up arrow moves back, and both wrap around. Enter takes the highlighted option, Escape closes the list and keeps the focus, Home and End jump to the first and last option, and Tab closes the list and moves on. In a multiple combobox, Backspace on an empty field removes the last chip. Disabled options are skipped.
Accessibility
The component follows the WAI-ARIA combobox pattern. The field is a text input with role="combobox" that says whether the list is open and which list it controls; the list is a listbox whose options carry their selected state, and in a multiple combobox the listbox is marked as allowing several. Focus never leaves the input: the highlighted option is pointed at with aria-activedescendant, so typing and moving through the list stay in one place. The chips of a multiple combobox each have a remove button named after the option, such as "Remove Svelte", and the clear button has its own name. The list is placed with a fixed strategy, so it flips and shifts to stay on screen instead of being cut off inside a scrolling box.