<script lang="ts">
import * as Combobox from "kumo-svelte/components/combobox";
const fruits = ["Apple", "Apricot", "Banana", "Blueberry", "Cherry", "Mango", "Orange", "Pear"];
let value = $state("Apple");
let open = $state(false);
</script>
<Combobox.Root bind:value bind:open>
<Combobox.TriggerInput placeholder="Please select" />
<Combobox.Content>
<Combobox.Empty />
<Combobox.List>
{#each fruits as fruit}
<Combobox.Item value={fruit}>{fruit}</Combobox.Item>
{/each}
</Combobox.List>
</Combobox.Content>
</Combobox.Root>
Import
import * as Combobox from "kumo-svelte/components/combobox"; Usage
<script lang="ts">
import * as Combobox from "kumo-svelte/components/combobox";
const fruits = ["Apple", "Banana", "Cherry", "Date", "Elderberry"];
let value = $state("Apple");
let open = $state(false);
</script>
<Combobox.Root bind:value bind:open>
<Combobox.TriggerInput placeholder="Select a fruit" />
<Combobox.Content>
<Combobox.List>
{#each fruits as fruit}
<Combobox.Item value={fruit}>{fruit}</Combobox.Item>
{/each}
</Combobox.List>
</Combobox.Content>
</Combobox.Root> Examples
Sizes
The Combobox supports four size variants that match the Input component: xs, sm, base (default), and lg.
<script lang="ts">
import * as Combobox from "kumo-svelte/components/combobox";
const fruits = ["Apple", "Apricot", "Banana", "Blueberry", "Cherry", "Mango", "Orange", "Pear"];
const sizeFruits = fruits.slice(0, 8);
let smValue = $state("");
let baseValue = $state("");
</script>
<div class="flex flex-wrap items-center gap-4">
<Combobox.Root bind:value={smValue} size="sm">
<Combobox.TriggerInput placeholder="Small (sm)" />
<Combobox.Content>
<Combobox.Empty />
<Combobox.List>
{#each sizeFruits as fruit}
<Combobox.Item value={fruit}>{fruit}</Combobox.Item>
{/each}
</Combobox.List>
</Combobox.Content>
</Combobox.Root>
<Combobox.Root bind:value={baseValue} size="base">
<Combobox.TriggerInput placeholder="Base (default)" />
<Combobox.Content>
<Combobox.Empty />
<Combobox.List>
{#each sizeFruits as fruit}
<Combobox.Item value={fruit}>{fruit}</Combobox.Item>
{/each}
</Combobox.List>
</Combobox.Content>
</Combobox.Root>
</div>
Size also applies to Combobox.TriggerValue for the searchable-inside variant.
<script lang="ts">
import * as Combobox from "kumo-svelte/components/combobox";
const languages = [
{ value: "en", label: "English", emoji: "🇬🇧" },
{ value: "fr", label: "French", emoji: "🇫🇷" },
{ value: "de", label: "German", emoji: "🇩🇪" },
{ value: "es", label: "Spanish", emoji: "🇪🇸" },
{ value: "ja", label: "Japanese", emoji: "🇯🇵" },
];
const languageItems = languages.map((language) => ({
label: `${language.emoji} ${language.label}`,
value: language.value,
}));
let smValue = $state("en");
let baseValue = $state("fr");
let smLabel = $derived(languageItems.find((language) => language.value === smValue)?.label);
let baseLabel = $derived(languageItems.find((language) => language.value === baseValue)?.label);
</script>
<div class="flex flex-wrap items-center gap-4">
<Combobox.Root bind:value={smValue} size="sm">
<Combobox.TriggerValue class="w-[160px]">{smLabel}</Combobox.TriggerValue>
<Combobox.Content>
<Combobox.Input placeholder="Search" />
<Combobox.Empty />
<Combobox.List>
{#each languages as language}
<Combobox.Item value={language.value} label={`${language.emoji} ${language.label}`}>
{language.emoji} {language.label}
</Combobox.Item>
{/each}
</Combobox.List>
</Combobox.Content>
</Combobox.Root>
<Combobox.Root bind:value={baseValue} size="base">
<Combobox.TriggerValue class="w-[180px]">{baseLabel}</Combobox.TriggerValue>
<Combobox.Content>
<Combobox.Input placeholder="Search" />
<Combobox.Empty />
<Combobox.List>
{#each languages as language}
<Combobox.Item value={language.value} label={`${language.emoji} ${language.label}`}>
{language.emoji} {language.label}
</Combobox.Item>
{/each}
</Combobox.List>
</Combobox.Content>
</Combobox.Root>
</div>
Searchable Item (Inside)
Use Combobox.TriggerValue for a select-style trigger and put Combobox.Input inside the popup to filter options.
<script lang="ts">
import * as Combobox from "kumo-svelte/components/combobox";
const languages = [
{ value: "en", label: "English", emoji: "🇬🇧" },
{ value: "fr", label: "French", emoji: "🇫🇷" },
{ value: "de", label: "German", emoji: "🇩🇪" },
{ value: "es", label: "Spanish", emoji: "🇪🇸" },
{ value: "ja", label: "Japanese", emoji: "🇯🇵" },
];
const languageItems = languages.map((language) => ({
label: `${language.emoji} ${language.label}`,
value: language.value,
}));
let value = $state("en");
let open = $state(false);
let selectedLabel = $derived(languageItems.find((language) => language.value === value)?.label);
</script>
<Combobox.Root bind:value bind:open>
<Combobox.TriggerValue class="w-[200px]">{selectedLabel}</Combobox.TriggerValue>
<Combobox.Content>
<Combobox.Input placeholder="Search languages" />
<Combobox.Empty />
<Combobox.List>
{#each languages as language}
<Combobox.Item value={language.value} label={`${language.emoji} ${language.label}`}>
{language.emoji} {language.label}
</Combobox.Item>
{/each}
</Combobox.List>
</Combobox.Content>
</Combobox.Root>
Searchable Select with Placeholder
Use Combobox.TriggerValue with a placeholder prop to create a searchable Select-style field. The placeholder is displayed until a value is selected.
<script lang="ts">
import * as Combobox from "kumo-svelte/components/combobox";
const languages = [
{ value: "en", label: "English", emoji: "🇬🇧" },
{ value: "fr", label: "French", emoji: "🇫🇷" },
{ value: "de", label: "German", emoji: "🇩🇪" },
{ value: "es", label: "Spanish", emoji: "🇪🇸" },
{ value: "ja", label: "Japanese", emoji: "🇯🇵" },
];
const languageItems = languages.map((language) => ({
label: `${language.emoji} ${language.label}`,
value: language.value,
}));
let value = $state("");
let open = $state(false);
let selectedLabel = $derived(languageItems.find((language) => language.value === value)?.label);
</script>
<Combobox.Root bind:value bind:open>
<Combobox.TriggerValue class="w-[200px]" placeholder="Select a language">
{selectedLabel}
</Combobox.TriggerValue>
<Combobox.Content>
<Combobox.Input placeholder="Search languages" />
<Combobox.Empty />
<Combobox.List>
{#each languages as language}
<Combobox.Item value={language.value} label={`${language.emoji} ${language.label}`}>
{language.emoji} {language.label}
</Combobox.Item>
{/each}
</Combobox.List>
</Combobox.Content>
</Combobox.Root>
Custom Trigger
Use Combobox.Trigger with a child snippet to replace the default input-like trigger with your own element. Pair it with your selected value label. This is useful for account switchers, sidebar navigation, or anywhere the default chrome does not fit.
<script lang="ts">
import { Button } from "kumo-svelte/components/button";
import * as Combobox from "kumo-svelte/components/combobox";
import CaretUpDownIcon from "phosphor-svelte/lib/CaretUpDownIcon";
const languages = [
{ value: "en", label: "English", emoji: "🇬🇧" },
{ value: "fr", label: "French", emoji: "🇫🇷" },
{ value: "de", label: "German", emoji: "🇩🇪" },
{ value: "es", label: "Spanish", emoji: "🇪🇸" },
{ value: "ja", label: "Japanese", emoji: "🇯🇵" },
];
const languageItems = languages.map((language) => ({
label: `${language.emoji} ${language.label}`,
value: language.value,
}));
let value = $state("en");
let open = $state(false);
let selectedLabel = $derived(languageItems.find((language) => language.value === value)?.label);
</script>
{#snippet triggerChild({ props }: { props: Record<string, unknown> })}
<Button {...props} variant="ghost" size="sm">
<span class="truncate">{selectedLabel}</span>
<CaretUpDownIcon size={14} class="shrink-0 text-kumo-subtle" />
</Button>
{/snippet}
<Combobox.Root bind:value bind:open>
<Combobox.Trigger child={triggerChild} />
<Combobox.Content>
<Combobox.Input placeholder="Search languages" />
<Combobox.Empty />
<Combobox.List>
{#each languages as language}
<Combobox.Item value={language.value} label={`${language.emoji} ${language.label}`}>
{language.emoji} {language.label}
</Combobox.Item>
{/each}
</Combobox.List>
</Combobox.Content>
</Combobox.Root>
Grouped
Group items into categories with Combobox.Group and Combobox.GroupLabel.
<script lang="ts">
import * as Combobox from "kumo-svelte/components/combobox";
const servers = [
{
value: "Asia",
items: [
{ label: "Japan", value: "japan" },
{ label: "Singapore", value: "singapore" },
{ label: "India", value: "india" },
],
},
{
value: "Europe",
items: [
{ label: "Germany", value: "germany" },
{ label: "France", value: "france" },
{ label: "Netherlands", value: "netherlands" },
],
},
];
let value = $state("");
let open = $state(false);
</script>
<Combobox.Root bind:value bind:open>
<Combobox.TriggerInput class="w-[200px]" placeholder="Select server" />
<Combobox.Content>
<Combobox.Empty />
<Combobox.List>
{#each servers as group}
<Combobox.Group>
<Combobox.GroupLabel>{group.value}</Combobox.GroupLabel>
{#each group.items as item}
<Combobox.Item value={item.value}>{item.label}</Combobox.Item>
{/each}
</Combobox.Group>
{/each}
</Combobox.List>
</Combobox.Content>
</Combobox.Root>
Multiple
Allow users to select multiple options from the list with multiple and a string[] value.
<script lang="ts">
import { Button } from "kumo-svelte/components/button";
import { Text } from "kumo-svelte/components/text";
import * as Combobox from "kumo-svelte/components/combobox";
const bots = [
{ value: "googlebot", label: "Googlebot", author: "Google" },
{ value: "bingbot", label: "Bingbot", author: "Microsoft" },
{ value: "duckduckbot", label: "DuckDuckBot", author: "DuckDuckGo" },
{ value: "slackbot", label: "Slackbot", author: "Slack" },
{ value: "discordbot", label: "Discordbot", author: "Discord" },
];
let value = $state<string[]>([]);
let open = $state(false);
let botLabels = $derived(Object.fromEntries(bots.map((bot) => [bot.value, bot.label])));
</script>
{#snippet selectedBot(value: string)}
<Combobox.Chip {value}>{botLabels[value] ?? value}</Combobox.Chip>
{/snippet}
<div class="flex gap-2">
<Combobox.Root multiple bind:value bind:open>
<Combobox.TriggerMultipleWithInput class="w-[400px]" placeholder="Select bots" renderItem={selectedBot} />
<Combobox.Content class="max-h-[200px] min-w-auto overflow-y-auto">
<Combobox.Empty />
<Combobox.List>
{#each bots as bot}
<Combobox.Item value={bot.value} label={bot.label}>
<div class="flex gap-2">
<Text>{bot.label}</Text>
<Text variant="secondary">{bot.author}</Text>
</div>
</Combobox.Item>
{/each}
</Combobox.List>
</Combobox.Content>
</Combobox.Root>
<Button variant="primary">Submit</Button>
</div>
With Field
Add label, description, and required state using the built-in Field wrapper.
Select your preferred database
<script lang="ts">
import * as Combobox from "kumo-svelte/components/combobox";
const databases = [
{ value: "postgres", label: "PostgreSQL" },
{ value: "mysql", label: "MySQL" },
{ value: "mongodb", label: "MongoDB" },
{ value: "redis", label: "Redis" },
{ value: "sqlite", label: "SQLite" },
{ value: "d1", label: "Cloudflare D1" },
];
let value = $state("");
let open = $state(false);
</script>
<Combobox.Root
bind:value
bind:open
label="Database"
description="Select your preferred database"
>
<Combobox.TriggerInput placeholder="Select database" />
<Combobox.Content>
<Combobox.Empty />
<Combobox.List>
{#each databases as database}
<Combobox.Item value={database.value}>{database.label}</Combobox.Item>
{/each}
</Combobox.List>
</Combobox.Content>
</Combobox.Root>
Disabled
Pass the disabled prop to prevent interaction.
<script lang="ts">
import * as Combobox from "kumo-svelte/components/combobox";
const fruits = ["Apple", "Apricot", "Banana", "Blueberry", "Cherry", "Mango", "Orange", "Pear"];
const languages = [
{ value: "en", label: "English", emoji: "🇬🇧" },
{ value: "fr", label: "French", emoji: "🇫🇷" },
{ value: "de", label: "German", emoji: "🇩🇪" },
{ value: "es", label: "Spanish", emoji: "🇪🇸" },
{ value: "ja", label: "Japanese", emoji: "🇯🇵" },
];
const languageItems = languages.map((language) => ({
label: `${language.emoji} ${language.label}`,
value: language.value,
}));
let selectedLanguageLabel = $derived(languageItems.find((language) => language.value === "en")?.label);
</script>
<div class="flex flex-wrap items-start gap-4">
<Combobox.Root disabled value="Apple">
<Combobox.TriggerInput class="w-[200px]" placeholder="Select fruit" />
<Combobox.Content>
<Combobox.Empty />
<Combobox.List>
{#each fruits as fruit}
<Combobox.Item value={fruit}>{fruit}</Combobox.Item>
{/each}
</Combobox.List>
</Combobox.Content>
</Combobox.Root>
<Combobox.Root disabled value="en">
<Combobox.TriggerValue class="w-[200px]">{selectedLanguageLabel}</Combobox.TriggerValue>
<Combobox.Content>
<Combobox.Input placeholder="Search" />
<Combobox.Empty />
<Combobox.List>
{#each languages as language}
<Combobox.Item value={language.value} label={`${language.emoji} ${language.label}`}>
{language.emoji} {language.label}
</Combobox.Item>
{/each}
</Combobox.List>
</Combobox.Content>
</Combobox.Root>
</div>
Disabled Items
Pass the disabled prop to an individual Combobox.Item to make it non-selectable. Disabled rows are rendered with a muted style and skipped during keyboard navigation selection.
<script lang="ts">
import { Text } from "kumo-svelte/components/text";
import * as Combobox from "kumo-svelte/components/combobox";
const disabledDatabases = [
{ value: "postgres", label: "PostgreSQL" },
{ value: "mysql", label: "MySQL" },
{ value: "mariadb", label: "MariaDB", disabled: true, reason: "Beta" },
{ value: "redis", label: "Redis" },
{ value: "d1", label: "Cloudflare D1" },
];
let value = $state("");
let open = $state(false);
</script>
<div class="w-80">
<Combobox.Root bind:value bind:open>
<Combobox.TriggerInput placeholder="Select database" />
<Combobox.Content>
<Combobox.Empty />
<Combobox.List>
{#each disabledDatabases as item}
<Combobox.Item value={item.value} disabled={item.disabled}>
{item.label}
{#if item.reason}
<Text variant="secondary" size="xs" as="span"> — {item.reason}</Text>
{/if}
</Combobox.Item>
{/each}
</Combobox.List>
</Combobox.Content>
</Combobox.Root>
</div>
Error State
Display validation errors with the root error prop.
Please select a database
<script lang="ts">
import * as Combobox from "kumo-svelte/components/combobox";
const databases = [
{ value: "postgres", label: "PostgreSQL" },
{ value: "mysql", label: "MySQL" },
{ value: "mongodb", label: "MongoDB" },
{ value: "redis", label: "Redis" },
{ value: "sqlite", label: "SQLite" },
{ value: "d1", label: "Cloudflare D1" },
];
let value = $state("");
let open = $state(false);
</script>
<Combobox.Root
bind:value
bind:open
label="Database"
error="Please select a database"
required
>
<Combobox.TriggerInput placeholder="Select database" />
<Combobox.Content>
<Combobox.Empty />
<Combobox.List>
{#each databases as database}
<Combobox.Item value={database.value}>{database.label}</Combobox.Item>
{/each}
</Combobox.List>
</Combobox.Content>
</Combobox.Root>
Customizing Dropdown Height
By default, Combobox.Content has a max height of 24rem (384px) or the available viewport space, whichever is smaller. The dropdown scrolls automatically when content exceeds this height.
To customize the max height, pass a class to Combobox.Content:
<!-- Shorter dropdown (200px) -->
<Combobox.Content class="max-h-[200px]">
<!-- Taller dropdown (500px) -->
<Combobox.Content class="max-h-[500px]">
<!-- Use Tailwind presets -->
<Combobox.Content class="max-h-64">
<Combobox.Content class="max-h-96"> API Reference
Combobox
Root component for the searchable select.
| Prop | Type | Default | Description |
|---|---|---|---|
| allowDeselect | boolean | false | Allow selecting the current single value again to clear it. |
| children | Snippet | — | Trigger and dropdown content. |
| defaultValue | string | string[] | — | Initial selected value. Use a string array when `multiple` is true. |
| description | Snippet | string | — | Helper text displayed below the combobox. |
| disabled | boolean | false | Disable the combobox and descendant controls. |
| error | string | { message: Snippet | string; match: FieldErrorMatch } | — | Error message or validation error object. |
| label | Snippet | string | — | Label content for the combobox. Enables the built-in Field wrapper. |
| labelTooltip | Snippet | — | Tooltip content displayed next to the label. |
| multiple | boolean | false | Allow multiple selections. Use a string array value. |
| name | string | — | Form field name. |
| onOpenChange | (open: boolean) => void | — | Called when the dropdown opens or closes. |
| onOpenChangeComplete | (open: boolean) => void | — | Called after the open or close transition completes. |
| onValueChange | (value: string | string[]) => void | — | Called when selection changes. |
| open | boolean | bindable | Dropdown open state. Bind with `bind:open` for two-way state. |
| required | boolean | — | Mark the field as required. |
| size | "xs" | "sm" | "base" | "lg" | "base" | Size of the combobox trigger. Matches Input component sizes. |
| value | string | string[] | "" or [] | Selected value. Bind with `bind:value` for two-way state. |
Combobox.Chip
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
| removeLabel | string | "Remove" |
| value* | string | — |
Combobox.Content
Dropdown container for the list.
| Prop | Type | Default | Description |
|---|---|---|---|
| children | Snippet | — | Dropdown content, usually input, empty state, and list. |
| class | string | — | Additional CSS classes. |
| container | PortalProps["to"] | — | Portal target. Defaults to the configured portal provider container. |
| sideOffset | number | 4 | Offset between trigger and content. |
Combobox.Empty
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
Combobox.Group
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
Combobox.GroupLabel
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
Combobox.Input
| Prop | Type | Default |
|---|---|---|
| aria-label | string | — |
| autocomplete | HTMLInputAttributes["autocomplete"] | — |
| class | string | — |
| clearOnDeselect | boolean | true |
| defaultValue | string | — |
| disabled | boolean | — |
| id | string | — |
| name | string | — |
| onblur | (event: FocusEvent & { currentTarget: HTMLInputElement }) => void | — |
| oninput | (event: Event & { currentTarget: HTMLInputElement }) => void | — |
| onValueChange | (value: string) => void | — |
| placeholder | string | — |
| required | boolean | — |
| size | KumoComboboxSize | — |
Combobox.Item
Individual selectable option.
| Prop | Type | Default | Description |
|---|---|---|---|
| children | Snippet | — | Custom item content. Defaults to `label` or `value`. |
| class | string | — | Additional CSS classes. |
| disabled | boolean | false | Disable this option. |
| label | string | — | Text used for filtering and accessibility. Defaults to `value`. |
| value* | string | — | Item value. |
Combobox.List
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
Combobox.Separator
| Prop | Type | Default |
|---|---|---|
| class | string | — |
Combobox.Trigger
| Prop | Type | Default |
|---|---|---|
| child | Snippet<[{ props: Record<string, unknown> }]> | — |
| children | Snippet | — |
| class | string | — |
| placeholder | string | — |
Combobox.TriggerInput
| Prop | Type | Default |
|---|---|---|
| aria-label | string | — |
| autocomplete | HTMLInputAttributes["autocomplete"] | — |
| class | string | — |
| clearLabel | string | "Clear selection" |
| clearOnDeselect | boolean | true |
| defaultValue | string | — |
| disabled | boolean | — |
| onblur | (event: FocusEvent & { currentTarget: HTMLInputElement }) => void | — |
| oninput | (event: Event & { currentTarget: HTMLInputElement }) => void | — |
| onValueChange | (value: string) => void | — |
| placeholder | string | — |
| showOptionsLabel | string | "Show options" |
Combobox.TriggerMultipleWithInput
| Prop | Type | Default |
|---|---|---|
| children | Snippet<[value: string]> | — |
| class | string | — |
| clearOnDeselect | boolean | true |
| disabled | boolean | — |
| inputSide | KumoComboboxInputSide | "right" |
| onblur | (event: FocusEvent & { currentTarget: HTMLInputElement }) => void | — |
| oninput | (event: Event & { currentTarget: HTMLInputElement }) => void | — |
| onValueChange | (value: string) => void | — |
| placeholder | string | — |
| renderItem | Snippet<[value: string]> | — |