Combobox
kumo-svelte
<script lang="ts">
  import { Combobox } from 'kumo-svelte';

  const fruits = [
  'Apple',
  'Apricot',
  'Avocado',
  'Banana',
  'Blackberry',
  'Blueberry',
  'Cantaloupe',
  'Cherry',
  'Coconut',
  'Cranberry',
  'Date',
  'Dragon Fruit',
  'Fig',
  'Grape',
  'Grapefruit',
  'Guava',
  'Honeydew',
  'Kiwi',
  'Lemon',
  'Lime',
  'Lychee',
  'Mango',
  'Nectarine',
  'Orange',
  'Papaya',
  'Passion Fruit',
  'Peach',
  'Pear',
  'Pineapple',
  'Plum',
  'Raspberry',
  'Strawberry',
  'Watermelon'
];

  let value = $state<string | null>('Apple');
</script>

<Combobox bind:value items={fruits}>
  <Combobox.TriggerInput placeholder="Please select" />
  <Combobox.Content>
    <Combobox.Empty />
    <Combobox.List>
      {#snippet children(item)}
        <Combobox.Item value={item}>{item}</Combobox.Item>
      {/snippet}
    </Combobox.List>
  </Combobox.Content>
</Combobox>

Installation

Barrel

import { Combobox } from 'kumo-svelte';

Granular

import { Combobox } from 'kumo-svelte/components/combobox';

Usage

<script lang="ts">  import { Combobox } from 'kumo-svelte';  const fruits = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'];  let value = $state<string | null>(null);</script><Combobox bind:value items={fruits}>  <Combobox.TriggerInput placeholder="Select a fruit" />  <Combobox.Content>    <Combobox.Empty />    <Combobox.List>      {#snippet children(item)}        <Combobox.Item value={item}>{item}</Combobox.Item>      {/snippet}    </Combobox.List>  </Combobox.Content></Combobox>

Examples

Sizes

The Combobox supports four size variants that match the Input component: xs, sm, base (default), and lg.

<script lang="ts">
  import { Combobox } from 'kumo-svelte';

  const fruits = [
  'Apple',
  'Apricot',
  'Avocado',
  'Banana',
  'Blackberry',
  'Blueberry',
  'Cantaloupe',
  'Cherry',
  'Coconut',
  'Cranberry',
  'Date',
  'Dragon Fruit',
  'Fig',
  'Grape',
  'Grapefruit',
  'Guava',
  'Honeydew',
  'Kiwi',
  'Lemon',
  'Lime',
  'Lychee',
  'Mango',
  'Nectarine',
  'Orange',
  'Papaya',
  'Passion Fruit',
  'Peach',
  'Pear',
  'Pineapple',
  'Plum',
  'Raspberry',
  'Strawberry',
  'Watermelon'
];
</script>

<div class="flex flex-wrap items-center gap-4">
  <Combobox size="sm" items={fruits.slice(0, 8)}>
    <Combobox.TriggerInput placeholder="Small (sm)" />
    <Combobox.Content>
      <Combobox.Empty />
      <Combobox.List>
        {#snippet children(item)}
          <Combobox.Item value={item}>{item}</Combobox.Item>
        {/snippet}
      </Combobox.List>
    </Combobox.Content>
  </Combobox>
  <Combobox size="base" items={fruits.slice(0, 8)}>
    <Combobox.TriggerInput placeholder="Base (default)" />
    <Combobox.Content>
      <Combobox.Empty />
      <Combobox.List>
        {#snippet children(item)}
          <Combobox.Item value={item}>{item}</Combobox.Item>
        {/snippet}
      </Combobox.List>
    </Combobox.Content>
  </Combobox>
</div>

Size also applies to TriggerValue (searchable inside variant):

<script lang="ts">
  import { Combobox } from 'kumo-svelte';

  const languages = [
    { value: 'en', label: 'English', emoji: 'GB' },
    { value: 'fr', label: 'French', emoji: 'FR' }
  ];
</script>

<div class="flex flex-wrap items-center gap-4">
  <Combobox size="sm" value={languages[0]} items={languages}>
    <Combobox.TriggerValue class="w-[160px]" />
    <Combobox.Content>
      <Combobox.Input placeholder="Search" />
      <Combobox.Empty />
      <Combobox.List>
        {#snippet children(item)}
          <Combobox.Item value={item}>{item.emoji} {item.label}</Combobox.Item>
        {/snippet}
      </Combobox.List>
    </Combobox.Content>
  </Combobox>
  <Combobox size="base" value={languages[1]} items={languages}>
    <Combobox.TriggerValue class="w-[180px]" />
    <Combobox.Content>
      <Combobox.Input placeholder="Search" />
      <Combobox.Empty />
      <Combobox.List>
        {#snippet children(item)}
          <Combobox.Item value={item}>{item.emoji} {item.label}</Combobox.Item>
        {/snippet}
      </Combobox.List>
    </Combobox.Content>
  </Combobox>
</div>

Searchable Item (Inside)

A searchable select component inside popup that allows users to filter and select.

<script lang="ts">
  import { Combobox } from 'kumo-svelte';

  const languages = [
    { value: 'en', label: 'English', emoji: 'GB' },
    { value: 'fr', label: 'French', emoji: 'FR' },
    { value: 'de', label: 'German', emoji: 'DE' },
    { value: 'es', label: 'Spanish', emoji: 'ES' }
  ];

  let value = $state<any>(languages[0]);
</script>

<Combobox bind:value items={languages}>
  <Combobox.TriggerValue class="w-[200px]" />
  <Combobox.Content>
    <Combobox.Input placeholder="Search languages" />
    <Combobox.Empty />
    <Combobox.List>
      {#snippet children(item)}
        <Combobox.Item value={item}>{item.emoji} {item.label}</Combobox.Item>
      {/snippet}
    </Combobox.List>
  </Combobox.Content>
</Combobox>

Searchable Select with Placeholder

Use 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 { Combobox } from 'kumo-svelte';

  const languages = [
    { value: 'en', label: 'English', emoji: 'GB' },
    { value: 'fr', label: 'French', emoji: 'FR' },
    { value: 'de', label: 'German', emoji: 'DE' },
    { value: 'es', label: 'Spanish', emoji: 'ES' }
  ];

  let value = $state<any>(null);
</script>

<Combobox bind:value items={languages}>
  <Combobox.TriggerValue class="w-[200px]" placeholder="Select a language" />
  <Combobox.Content>
    <Combobox.Input placeholder="Search languages" />
    <Combobox.Empty />
    <Combobox.List>
      {#snippet children(item)}
        <Combobox.Item value={item}>{item.emoji} {item.label}</Combobox.Item>
      {/snippet}
    </Combobox.List>
  </Combobox.Content>
</Combobox>

Custom Trigger

Use Combobox.Trigger with a render prop to replace the default input-like trigger with your own element. Pair with Combobox.Value to display the selected value. Useful for account switchers, sidebar navigation, or anywhere the default chrome doesn't fit.

<script lang="ts">
  import { Combobox } from 'kumo-svelte';
  import { MagnifyingGlass } from 'phosphor-svelte';

  const languages = [
    { value: 'en', label: 'English', emoji: 'GB' },
    { value: 'fr', label: 'French', emoji: 'FR' }
  ];

  let value = $state(languages[0]);
</script>

<Combobox bind:value items={languages}>
  <Combobox.Trigger class="rounded-md px-2 py-1 text-sm hover:bg-kumo-fill-hover">
    <Combobox.Value>
      <span class="truncate">{value.emoji} {value.label}</span>
    </Combobox.Value>
    <MagnifyingGlass class="size-3.5 shrink-0 text-kumo-subtle" />
  </Combobox.Trigger>
  <Combobox.Content>
    <Combobox.Input placeholder="Search languages" />
    <Combobox.Empty />
    <Combobox.List>
      {#snippet children(item)}
        <Combobox.Item value={item}>{item.emoji} {item.label}</Combobox.Item>
      {/snippet}
    </Combobox.List>
  </Combobox.Content>
</Combobox>

Grouped

Group items into categories using the Group and GroupLabel components.

<script lang="ts">
  import { Combobox } from 'kumo-svelte';

  const servers = [
    { value: 'Asia', items: [{ label: 'Japan', value: 'japan' }, { label: 'Singapore', value: 'singapore' }] },
    { value: 'Europe', items: [{ label: 'Germany', value: 'germany' }, { label: 'France', value: 'france' }] }
  ];
</script>

<Combobox items={servers}>
  <Combobox.TriggerInput class="w-[200px]" placeholder="Select server" />
  <Combobox.Content>
    <Combobox.Empty />
    <Combobox.List>
      {#snippet children(group)}
        <Combobox.Group items={group.items}>
          <Combobox.GroupLabel>{group.value}</Combobox.GroupLabel>
          <Combobox.Collection>
            {#snippet children(item)}
              <Combobox.Item value={item}>{item.label}</Combobox.Item>
            {/snippet}
          </Combobox.Collection>
        </Combobox.Group>
      {/snippet}
    </Combobox.List>
  </Combobox.Content>
</Combobox>

Multiple

Allow users to select multiple options from the list.

<script lang="ts">
  import { Button, Combobox, Text } from 'kumo-svelte';

  const bots = [
    { value: 'googlebot', label: 'Googlebot', author: 'Google' },
    { value: 'bingbot', label: 'Bingbot', author: 'Microsoft' }
  ];

  let value = $state<any[]>([]);
</script>

<div class="flex gap-2">
  <Combobox bind:value items={bots} multiple>
    <Combobox.TriggerMultipleWithInput class="w-[400px]" placeholder="Select bots">
      {#snippet children(selected)}
        <Combobox.Chip value={selected}>{selected.label}</Combobox.Chip>
      {/snippet}
    </Combobox.TriggerMultipleWithInput>
    <Combobox.Content class="max-h-[200px] min-w-auto overflow-y-auto">
      <Combobox.Empty />
      <Combobox.List>
        {#snippet children(item)}
          <Combobox.Item value={item}>
            <div class="flex gap-2">
              <Text>{item.label}</Text>
              <Text variant="secondary">{item.author}</Text>
            </div>
          </Combobox.Item>
        {/snippet}
      </Combobox.List>
    </Combobox.Content>
  </Combobox>
  <Button variant="primary">Submit</Button>
</div>

With Field

Add label and description using the built-in Field wrapper.

Select your preferred database
<script lang="ts">
  import { Combobox } from 'kumo-svelte';

  const databases = [
    { value: 'postgres', label: 'PostgreSQL' },
    { value: 'mysql', label: 'MySQL' },
    { value: 'redis', label: 'Redis' }
  ];
</script>

<div class="w-80">
  <Combobox
    items={databases}
    label="Database"
    description="Select your preferred database"
  >
    <Combobox.TriggerInput placeholder="Select database" />
    <Combobox.Content>
      <Combobox.Empty />
      <Combobox.List>
        {#snippet children(item)}
          <Combobox.Item value={item}>{item.label}</Combobox.Item>
        {/snippet}
      </Combobox.List>
    </Combobox.Content>
  </Combobox>
</div>

Disabled

Pass the disabled prop to prevent interaction. Works with both TriggerInput and TriggerValue.

<script lang="ts">
  import { Combobox } from 'kumo-svelte';

  const fruits = [
  'Apple',
  'Apricot',
  'Avocado',
  'Banana',
  'Blackberry',
  'Blueberry',
  'Cantaloupe',
  'Cherry',
  'Coconut',
  'Cranberry',
  'Date',
  'Dragon Fruit',
  'Fig',
  'Grape',
  'Grapefruit',
  'Guava',
  'Honeydew',
  'Kiwi',
  'Lemon',
  'Lime',
  'Lychee',
  'Mango',
  'Nectarine',
  'Orange',
  'Papaya',
  'Passion Fruit',
  'Peach',
  'Pear',
  'Pineapple',
  'Plum',
  'Raspberry',
  'Strawberry',
  'Watermelon'
];

  const languages = [
    { value: 'en', label: 'English', emoji: 'GB' },
    { value: 'fr', label: 'French', emoji: 'FR' }
  ];
</script>

<div class="flex flex-wrap items-start gap-4">
  <Combobox value="Apple" items={fruits} disabled>
    <Combobox.TriggerInput class="w-[200px]" placeholder="Select fruit" />
    <Combobox.Content>
      <Combobox.Empty />
      <Combobox.List>
        {#snippet children(item)}
          <Combobox.Item value={item}>{item}</Combobox.Item>
        {/snippet}
      </Combobox.List>
    </Combobox.Content>
  </Combobox>

  <Combobox value={languages[0]} items={languages} disabled>
    <Combobox.TriggerValue class="w-[200px]" />
    <Combobox.Content>
      <Combobox.Input placeholder="Search" />
      <Combobox.Empty />
      <Combobox.List>
        {#snippet children(item)}
          <Combobox.Item value={item}>{item.emoji} {item.label}</Combobox.Item>
        {/snippet}
      </Combobox.List>
    </Combobox.Content>
  </Combobox>
</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 { Combobox, Text } from 'kumo-svelte';

  const items = [
    { value: 'postgres', label: 'PostgreSQL' },
    { value: 'mysql', label: 'MySQL' },
    { value: 'mariadb', label: 'MariaDB', disabled: true, reason: 'Beta' },
    { value: 'mongodb', label: 'MongoDB' },
    { value: 'cassandra', label: 'Apache Cassandra', disabled: true, reason: 'Coming soon' }
  ];
</script>

<div class="w-80">
  <Combobox items={items}>
    <Combobox.TriggerInput placeholder="Select database" />
    <Combobox.Content>
      <Combobox.Empty />
      <Combobox.List>
        {#snippet children(item)}
          <Combobox.Item value={item} disabled={item.disabled}>
            <span>
              {item.label}
              {#if item.reason}
                <Text variant="secondary" size="xs" as="span"> - {item.reason}</Text>
              {/if}
            </span>
          </Combobox.Item>
        {/snippet}
      </Combobox.List>
    </Combobox.Content>
  </Combobox>
</div>

Error State

Display validation errors with the error prop.

Please select a database
<script lang="ts">
  import { Combobox } from 'kumo-svelte';

  const databases = [
    { value: 'postgres', label: 'PostgreSQL' },
    { value: 'mysql', label: 'MySQL' },
    { value: 'redis', label: 'Redis' }
  ];
</script>

<div class="w-80">
  <Combobox
    items={databases}
    label="Database"
    error={{ message: "Please select a database", match: true }}
  >
    <Combobox.TriggerInput placeholder="Select database" />
    <Combobox.Content>
      <Combobox.Empty />
      <Combobox.List>
        {#snippet children(item)}
          <Combobox.Item value={item}>{item.label}</Combobox.Item>
        {/snippet}
      </Combobox.List>
    </Combobox.Content>
  </Combobox>
</div>

Filtering

Filtering is case- and accent-insensitive by default, powered by Intl.Collator under the hood. For string items, no custom filter is needed.

When filtering on a property of object items, use Combobox.useFilter() to preserve the built-in accent-insensitive matching:

<script lang="ts">  import { Combobox } from "kumo-svelte";  const { contains } = Combobox.useFilter();  const languages = [    { value: "pt", label: "Portuguese", emoji: "🇵🇹" },    { value: "es", label: "Spanish", emoji: "🇪🇸" }  ];  const filter = (item, query) => contains(item.label, query);</script><Combobox items={languages} {filter}>  <!-- ... --></Combobox>

To disable filtering entirely (for example, when results come from a server), pass filter={null}:

<Combobox items={results} filter={null}>  <!-- ... --></Combobox>

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">  // 256px<Combobox.Content class="max-h-96">  // 384px (same as default)

API Reference

Combobox

Root component for the searchable select.

PropTypeDefaultDescription
size 'xs' | 'sm' | 'base' | 'lg'"base"Size preset.
items *unknown[]-Array of items to display in the dropdown.
value unknown-Controlled value.
children Snippet-Child snippet rendered by the component.
class string-Additional classes merged onto the root element.
label string | Snippet-Visible label content.
required boolean-Marks the field as required.
labelTooltip string | Snippet-Optional help content for the label.
description string | Snippet-Supporting description text.
error FieldError-Validation error message or matcher.
onValueChange (value: unknown) => void-Called when the value changes.
multiple booleanfalseEnables multiple selection.
onOpenChange (open: boolean) => void-Called when open state changes.

Combobox.Content

Dropdown container for the list.

PropTypeDefaultDescription
align 'start' | 'center' | 'end'"start"Alignment of the popup relative to the trigger.
alignOffset number | string-Offset along the alignment axis.
side 'top' | 'right' | 'bottom' | 'left'"bottom"Side of the trigger where the popup is placed.
sideOffset number | string4Offset between the popup and the trigger.

Combobox.Item

Individual selectable option.

PropTypeDefaultDescription
value *ComboboxItem-Controlled value.
disabled boolean-Disables the component.

Additional Sub-components

  • `Combobox.TriggerInput` - Single-select input trigger
  • `Combobox.TriggerValue` - Button trigger showing selected value
  • `Combobox.TriggerMultipleWithInput` - Multi-select with chips
  • `Combobox.Input` - Search input inside dropdown
  • `Combobox.List` - List container with render prop
  • `Combobox.Group` - Group container for categorized items
  • `Combobox.GroupLabel` - Header label for a group
  • `Combobox.Collection` - Items container within a group
  • `Combobox.Chip` - Selected item chip
  • `Combobox.Empty` - Empty state message