Combobox
Autocomplete input with a list of suggestions.
The Combobox component is built on top of Kobalte Combobox, which provides the accessible autocomplete primitive and keyboard navigation.
import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem,} from "~/components/ui/combobox";
const frameworks = ["Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro"];
export default function ComboboxDemo() { return ( <Combobox options={frameworks} placeholder="Select a framework..." itemComponent={(props) => ( <ComboboxItem item={props.item}>{props.item.rawValue}</ComboboxItem> )} > <ComboboxInput placeholder="Select a framework..." /> <ComboboxContent> <ComboboxEmpty>No frameworks found.</ComboboxEmpty> </ComboboxContent> </Combobox> );}Installation
Usage
import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem,} from "~/components/ui/combobox";
const frameworks = ["Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro"];<Combobox options={frameworks} placeholder="Select a framework..." itemComponent={(props) => ( <ComboboxItem item={props.item}>{props.item.rawValue}</ComboboxItem> )}> <ComboboxInput placeholder="Select a framework..." /> <ComboboxContent> <ComboboxEmpty>No frameworks found.</ComboboxEmpty> </ComboboxContent></Combobox>See the Kobalte Combobox documentation for primitive-level options, accessibility, and keyboard behavior.
Composition
Simple
A single-line input and a flat list rendered from options via itemComponent (see Basic).
Combobox├── ComboboxInput└── ComboboxContent └── ComboboxEmptyWith Chips
Combobox├── ComboboxChips│ ├── ComboboxValue│ │ └── ComboboxChip│ └── ComboboxChipsInput└── ComboboxContentWith Groups
Group options with optionGroupChildren, and render a section heading (and an optional separator) from sectionComponent (see Groups).
Combobox├── ComboboxInput└── ComboboxContent └── ComboboxEmpty
sectionComponent renders, per group:├── ComboboxSeparator (optional, skipped for the first group)└── ComboboxSection └── ComboboxSectionLabelCustom Items
Use optionValue and optionTextValue when your options are objects.
type Framework = { label: string; value: string;};
const frameworks: Framework[] = [ { label: "Next.js", value: "next" }, { label: "SvelteKit", value: "sveltekit" }, { label: "Nuxt", value: "nuxt" },];
<Combobox options={frameworks} optionValue="value" optionTextValue="label" placeholder="Select a framework..." itemComponent={(props) => ( <ComboboxItem item={props.item}>{props.item.rawValue.label}</ComboboxItem> )}> <ComboboxInput placeholder="Select a framework..." /> <ComboboxContent> <ComboboxEmpty>No frameworks found.</ComboboxEmpty> </ComboboxContent></Combobox>Multiple Selection
Use multiple with ComboboxChips, ComboboxValue, ComboboxChip, and ComboboxChipsInput, following the shadcn composition. Selected chips remain visible while filtering or opening and closing the list.
import { For } from "solid-js";import { Combobox, ComboboxChip, ComboboxChips, ComboboxChipsInput, ComboboxContent, ComboboxItem, ComboboxValue,} from "~/components/ui/combobox";
const frameworks = ["Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro"];
<Combobox options={frameworks} multiple defaultValue={[frameworks[0]]} itemComponent={(props) => ( <ComboboxItem item={props.item}>{props.item.rawValue}</ComboboxItem> )}> <ComboboxChips> <ComboboxValue<string>> {(values) => ( <For each={values}> {(value) => <ComboboxChip value={value} removeLabel={`Remove ${value}`}>{value}</ComboboxChip>} </For> )} </ComboboxValue> <ComboboxChipsInput placeholder="Add framework..." /> </ComboboxChips> <ComboboxContent /></Combobox>Pass the selected option as each chip's value: Kobalte removes options by value rather than by their rendered position. ComboboxChips also anchors the popup through Kobalte Control, so no useComboboxAnchor hook is needed.
ComboboxValue provides the selected options to its render function. Pass explicit chip children and a removeLabel for the remove button’s accessible name. Set showRemove={false} to hide a chip's remove button. The remove buttons have accessible names and support keyboard activation; removal returns focus to the input.
Use value and onChange for controlled selection, or defaultValue for uncontrolled selection. Set disabled or validationState="invalid" on Combobox to apply those states to the chips input. Disabled selections stay visible and cannot be removed.
Basic
A simple combobox with a list of frameworks.
import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem,} from "~/components/ui/combobox";
const frameworks = ["Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro"];
export default function ComboboxBasic() { return ( <Combobox options={frameworks} placeholder="Select a framework..." itemComponent={(props) => ( <ComboboxItem item={props.item}>{props.item.rawValue}</ComboboxItem> )} > <ComboboxInput placeholder="Select a framework..." /> <ComboboxContent> <ComboboxEmpty>No frameworks found.</ComboboxEmpty> </ComboboxContent> </Combobox> );}Multiple
A combobox with multiple selection using multiple and ComboboxChips, starting with a removable Next.js chip.
import { For } from "solid-js";import { Combobox, ComboboxChip, ComboboxChips, ComboboxChipsInput, ComboboxContent, ComboboxEmpty, ComboboxItem, ComboboxValue,} from "~/components/ui/combobox";
const frameworks = ["Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro"];
export default function ComboboxMultiple() { return ( <Combobox<(typeof frameworks)[number]> options={frameworks} placeholder="Select frameworks..." multiple triggerMode="focus" defaultValue={[frameworks[0]]} itemComponent={(props) => ( <ComboboxItem item={props.item}>{props.item.rawValue}</ComboboxItem> )} > <ComboboxChips class="w-full max-w-xs"> <ComboboxValue<string>> {(values) => ( <For each={values}> {(value) => ( <ComboboxChip value={value} removeLabel={`Remove ${value}`}> {value} </ComboboxChip> )} </For> )} </ComboboxValue> <ComboboxChipsInput placeholder="Add framework..." /> </ComboboxChips> <ComboboxContent> <ComboboxEmpty>No frameworks found.</ComboboxEmpty> </ComboboxContent> </Combobox> );}Clear Button
Use the showClear prop to show a clear button.
import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem,} from "~/components/ui/combobox";
const frameworks = ["Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro"];
export default function ComboboxClear() { return ( <Combobox options={frameworks} placeholder="Select a framework..." defaultValue={frameworks[0]} itemComponent={(props) => ( <ComboboxItem item={props.item}>{props.item.rawValue}</ComboboxItem> )} > <ComboboxInput placeholder="Select a framework..." showClear /> <ComboboxContent> <ComboboxEmpty>No frameworks found.</ComboboxEmpty> </ComboboxContent> </Combobox> );}Groups
Use optionGroupChildren with a sectionComponent rendering ComboboxSection, ComboboxSectionLabel, and ComboboxSeparator to group items.
import { Show } from "solid-js";import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxSection, ComboboxSectionLabel, ComboboxSeparator,} from "~/components/ui/combobox";
type TimezoneGroup = { label: string; options: string[];};
const timezones: TimezoneGroup[] = [ { label: "Americas", options: [ "(GMT-5) New York", "(GMT-8) Los Angeles", "(GMT-6) Chicago", "(GMT-5) Toronto", "(GMT-8) Vancouver", "(GMT-3) São Paulo", ], }, { label: "Europe", options: [ "(GMT+0) London", "(GMT+1) Paris", "(GMT+1) Berlin", "(GMT+1) Rome", "(GMT+1) Madrid", "(GMT+1) Amsterdam", ], }, { label: "Asia/Pacific", options: [ "(GMT+9) Tokyo", "(GMT+8) Shanghai", "(GMT+8) Singapore", "(GMT+4) Dubai", "(GMT+11) Sydney", "(GMT+9) Seoul", ], },];
export default function ComboboxGroups() { return ( <Combobox<string, TimezoneGroup> options={timezones} optionValue={(option) => option} optionTextValue={(option) => option} optionGroupChildren="options" placeholder="Select a timezone..." itemComponent={(props) => ( <ComboboxItem item={props.item}>{props.item.rawValue}</ComboboxItem> )} sectionComponent={(props) => ( <> <Show when={props.section.index !== 0}> <ComboboxSeparator /> </Show> <ComboboxSection> <ComboboxSectionLabel>{props.section.rawValue.label}</ComboboxSectionLabel> </ComboboxSection> </> )} > <ComboboxInput placeholder="Select a timezone..." /> <ComboboxContent> <ComboboxEmpty>No timezones found.</ComboboxEmpty> </ComboboxContent> </Combobox> );}Custom Items
You can render a custom component inside ComboboxItem via itemComponent.
import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem,} from "~/components/ui/combobox";import { Item, ItemContent, ItemDescription, ItemTitle } from "~/components/ui/item";
const countries = [ { code: "ar", value: "argentina", label: "Argentina", continent: "South America" }, { code: "au", value: "australia", label: "Australia", continent: "Oceania" }, { code: "br", value: "brazil", label: "Brazil", continent: "South America" }, { code: "ca", value: "canada", label: "Canada", continent: "North America" }, { code: "cn", value: "china", label: "China", continent: "Asia" }, { code: "co", value: "colombia", label: "Colombia", continent: "South America" }, { code: "eg", value: "egypt", label: "Egypt", continent: "Africa" }, { code: "fr", value: "france", label: "France", continent: "Europe" }, { code: "de", value: "germany", label: "Germany", continent: "Europe" }, { code: "it", value: "italy", label: "Italy", continent: "Europe" }, { code: "jp", value: "japan", label: "Japan", continent: "Asia" }, { code: "ke", value: "kenya", label: "Kenya", continent: "Africa" }, { code: "mx", value: "mexico", label: "Mexico", continent: "North America" }, { code: "nz", value: "new-zealand", label: "New Zealand", continent: "Oceania" }, { code: "ng", value: "nigeria", label: "Nigeria", continent: "Africa" }, { code: "za", value: "south-africa", label: "South Africa", continent: "Africa" }, { code: "kr", value: "south-korea", label: "South Korea", continent: "Asia" }, { code: "gb", value: "united-kingdom", label: "United Kingdom", continent: "Europe" }, { code: "us", value: "united-states", label: "United States", continent: "North America" },];
export default function ComboboxCustom() { return ( <Combobox<(typeof countries)[number]> options={countries} optionValue="value" optionTextValue="label" placeholder="Search countries..." itemComponent={(props) => ( <ComboboxItem item={props.item}> <Item size="xs" class="p-0"> <ItemContent> <ItemTitle class="whitespace-nowrap">{props.item.rawValue.label}</ItemTitle> <ItemDescription> {props.item.rawValue.continent} ({props.item.rawValue.code}) </ItemDescription> </ItemContent> </Item> </ComboboxItem> )} > <ComboboxInput placeholder="Search countries..." /> <ComboboxContent> <ComboboxEmpty>No countries found.</ComboboxEmpty> </ComboboxContent> </Combobox> );}Invalid
Use the validationState="invalid" prop to make the combobox invalid.
import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem,} from "~/components/ui/combobox";
const frameworks = ["Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro"];
export default function ComboboxInvalid() { return ( <Combobox options={frameworks} placeholder="Select a framework..." validationState="invalid" itemComponent={(props) => ( <ComboboxItem item={props.item}>{props.item.rawValue}</ComboboxItem> )} > <ComboboxInput placeholder="Select a framework..." aria-invalid="true" /> <ComboboxContent> <ComboboxEmpty>No frameworks found.</ComboboxEmpty> </ComboboxContent> </Combobox> );}Disabled
Use the disabled prop to disable the combobox.
import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem,} from "~/components/ui/combobox";
const frameworks = ["Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro"];
export default function ComboboxDisabled() { return ( <Combobox options={frameworks} placeholder="Select a framework..." disabled itemComponent={(props) => ( <ComboboxItem item={props.item}>{props.item.rawValue}</ComboboxItem> )} > <ComboboxInput placeholder="Select a framework..." disabled /> <ComboboxContent> <ComboboxEmpty>No frameworks found.</ComboboxEmpty> </ComboboxContent> </Combobox> );}Popup
Use ComboboxTrigger to open a combobox from a button instead of a text input.
import { createSignal } from "solid-js";import { Button } from "~/components/ui/button";import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxItem, ComboboxTrigger,} from "~/components/ui/combobox";
const countries = [ { code: "ar", value: "argentina", label: "Argentina" }, { code: "au", value: "australia", label: "Australia" }, { code: "br", value: "brazil", label: "Brazil" }, { code: "ca", value: "canada", label: "Canada" }, { code: "cn", value: "china", label: "China" }, { code: "fr", value: "france", label: "France" }, { code: "de", value: "germany", label: "Germany" }, { code: "jp", value: "japan", label: "Japan" }, { code: "gb", value: "united-kingdom", label: "United Kingdom" }, { code: "us", value: "united-states", label: "United States" },];
export default function ComboboxPopup() { const [value, setValue] = createSignal<(typeof countries)[number] | null>(null);
return ( <Combobox<(typeof countries)[number]> options={countries} value={value()} onChange={setValue} optionValue="value" optionTextValue="label" placeholder="Select country" itemComponent={(props) => ( <ComboboxItem item={props.item}>{props.item.rawValue.label}</ComboboxItem> )} > <ComboboxTrigger as={Button} variant="outline" class="w-64 justify-between font-normal"> {value()?.label ?? "Select country"} </ComboboxTrigger> <ComboboxContent> <ComboboxEmpty>No countries found.</ComboboxEmpty> </ComboboxContent> </Combobox> );}Input Group
Add an addon to the combobox by placing InputGroupAddon inside ComboboxInput.
import { GlobeIcon } from "lucide-solid";import { Show } from "solid-js";import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxSection, ComboboxSectionLabel, ComboboxSeparator,} from "~/components/ui/combobox";import { InputGroupAddon } from "~/components/ui/input-group";
type TimezoneGroup = { label: string; options: string[];};
const timezones: TimezoneGroup[] = [ { label: "Americas", options: [ "(GMT-5) New York", "(GMT-8) Los Angeles", "(GMT-6) Chicago", "(GMT-5) Toronto", "(GMT-8) Vancouver", "(GMT-3) São Paulo", ], }, { label: "Europe", options: [ "(GMT+0) London", "(GMT+1) Paris", "(GMT+1) Berlin", "(GMT+1) Rome", "(GMT+1) Madrid", "(GMT+1) Amsterdam", ], }, { label: "Asia/Pacific", options: [ "(GMT+9) Tokyo", "(GMT+8) Shanghai", "(GMT+8) Singapore", "(GMT+4) Dubai", "(GMT+11) Sydney", "(GMT+9) Seoul", ], },];
export default function ComboboxInputGroup() { return ( <Combobox<string, TimezoneGroup> options={timezones} optionValue={(option) => option} optionTextValue={(option) => option} optionGroupChildren="options" placeholder="Select a timezone..." itemComponent={(props) => ( <ComboboxItem item={props.item}>{props.item.rawValue}</ComboboxItem> )} sectionComponent={(props) => ( <> <Show when={props.section.index !== 0}> <ComboboxSeparator /> </Show> <ComboboxSection> <ComboboxSectionLabel>{props.section.rawValue.label}</ComboboxSectionLabel> </ComboboxSection> </> )} > <ComboboxInput placeholder="Select a timezone..."> <InputGroupAddon> <GlobeIcon class="size-4" /> </InputGroupAddon> </ComboboxInput> <ComboboxContent class="w-60"> <ComboboxEmpty>No timezones found.</ComboboxEmpty> </ComboboxContent> </Combobox> );}API Reference
See the Kobalte Combobox API reference for primitive props, keyboard behavior, and accessibility details.