import { useState } from "react";
import { Combobox } from "@cloudflare/kumo";

// Basic demo with TriggerInput
export function ComboboxDemo() {
  const [value, setValue] = useState<string | null>("Apple");

  return (
    <Combobox
      value={value}
      onValueChange={(v) => setValue(v as string | null)}
      items={fruits}
    >
      <Combobox.TriggerInput placeholder="Please select" />
      <Combobox.Content>
        <Combobox.Empty />
        <Combobox.List>
          {(item: string) => (
            <Combobox.Item key={item} value={item}>
              {item}
            </Combobox.Item>
          )}
        </Combobox.List>
      </Combobox.Content>
    </Combobox>
  );
}

Installation

Barrel

import { Combobox } from "@cloudflare/kumo";

Granular

import { Combobox } from "@cloudflare/kumo/components/combobox";

Usage

import { useState } from "react";
import { Combobox } from "@cloudflare/kumo";

const fruits = ["Apple", "Banana", "Cherry", "Date", "Elderberry"];

export default function Example() {
  const [value, setValue] = useState<string | null>(null);

  return (
    <Combobox value={value} onValueChange={setValue} items={fruits}>
      <Combobox.TriggerInput placeholder="Select a fruit" />
      <Combobox.Content>
        <Combobox.Empty />
        <Combobox.List>
          {(item) => (
            <Combobox.Item key={item} value={item}>
              {item}
            </Combobox.Item>
          )}
        </Combobox.List>
      </Combobox.Content>
    </Combobox>
  );
}

Examples

Sizes

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

import { useState } from "react";
import { Combobox } from "@cloudflare/kumo";

/** Demonstrates the different size variants: xs, sm, base, and lg. */
export function ComboboxSizesDemo() {
  const [smValue, setSmValue] = useState<string | null>(null);
  const [baseValue, setBaseValue] = useState<string | null>(null);

  return (
    <div className="flex flex-wrap items-center gap-4">
      <Combobox
        size="sm"
        value={smValue}
        onValueChange={(v) => setSmValue(v as string | null)}
        items={fruits.slice(0, 8)}
      >
        <Combobox.TriggerInput placeholder="Small (sm)" />
        <Combobox.Content>
          <Combobox.Empty />
          <Combobox.List>
            {(item: string) => (
              <Combobox.Item key={item} value={item}>
                {item}
              </Combobox.Item>
            )}
          </Combobox.List>
        </Combobox.Content>
      </Combobox>
      <Combobox
        size="base"
        value={baseValue}
        onValueChange={(v) => setBaseValue(v as string | null)}
        items={fruits.slice(0, 8)}
      >
        <Combobox.TriggerInput placeholder="Base (default)" />
        <Combobox.Content>
          <Combobox.Empty />
          <Combobox.List>
            {(item: string) => (
              <Combobox.Item key={item} value={item}>
                {item}
              </Combobox.Item>
            )}
          </Combobox.List>
        </Combobox.Content>
      </Combobox>
    </div>
  );
}

Size also applies to TriggerValue (searchable inside variant):

import { useState } from "react";
import { Combobox } from "@cloudflare/kumo";

/** Demonstrates size variants with TriggerValue (searchable inside). */
export function ComboboxSizesSearchableInsideDemo() {
  const [smValue, setSmValue] = useState<Language>(languages[0]);
  const [baseValue, setBaseValue] = useState<Language>(languages[1]);

  return (
    <div className="flex flex-wrap items-center gap-4">
      <Combobox
        size="sm"
        value={smValue}
        onValueChange={(v) => setSmValue(v as Language)}
        items={languages}
      >
        <Combobox.TriggerValue className="w-[160px]" />
        <Combobox.Content>
          <Combobox.Input placeholder="Search" />
          <Combobox.Empty />
          <Combobox.List>
            {(item: Language) => (
              <Combobox.Item key={item.value} value={item}>
                {item.emoji} {item.label}
              </Combobox.Item>
            )}
          </Combobox.List>
        </Combobox.Content>
      </Combobox>
      <Combobox
        size="base"
        value={baseValue}
        onValueChange={(v) => setBaseValue(v as Language)}
        items={languages}
      >
        <Combobox.TriggerValue className="w-[180px]" />
        <Combobox.Content>
          <Combobox.Input placeholder="Search" />
          <Combobox.Empty />
          <Combobox.List>
            {(item: Language) => (
              <Combobox.Item key={item.value} value={item}>
                {item.emoji} {item.label}
              </Combobox.Item>
            )}
          </Combobox.List>
        </Combobox.Content>
      </Combobox>
    </div>
  );
}

Searchable Item (Inside)

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

import { useState } from "react";
import { Combobox } from "@cloudflare/kumo";

// Searchable inside popup with TriggerValue
export function ComboboxSearchableInsideDemo() {
  const [value, setValue] = useState<Language>(languages[0]);

  return (
    <Combobox
      value={value}
      onValueChange={(v) => setValue(v as Language)}
      items={languages}
    >
      <Combobox.TriggerValue className="w-[200px]" />
      <Combobox.Content>
        <Combobox.Input placeholder="Search languages" />
        <Combobox.Empty />
        <Combobox.List>
          {(item: Language) => (
            <Combobox.Item key={item.value} value={item}>
              {item.emoji} {item.label}
            </Combobox.Item>
          )}
        </Combobox.List>
      </Combobox.Content>
    </Combobox>
  );
}

Grouped

Group items into categories using the Group and GroupLabel components.

import { useState } from "react";
import { Combobox } from "@cloudflare/kumo";

// Grouped items demo
export function ComboboxGroupedDemo() {
  const [value, setValue] = useState<ServerLocation | null>(null);

  return (
    <Combobox
      value={value}
      onValueChange={(v) => setValue(v as ServerLocation | null)}
      items={servers}
    >
      <Combobox.TriggerInput
        className="w-[200px]"
        placeholder="Select server"
      />
      <Combobox.Content>
        <Combobox.Empty />
        <Combobox.List>
          {(group: ServerLocationGroup) => (
            <Combobox.Group key={group.value} items={group.items}>
              <Combobox.GroupLabel>{group.value}</Combobox.GroupLabel>
              <Combobox.Collection>
                {(item: ServerLocation) => (
                  <Combobox.Item key={item.value} value={item}>
                    {item.label}
                  </Combobox.Item>
                )}
              </Combobox.Collection>
            </Combobox.Group>
          )}
        </Combobox.List>
      </Combobox.Content>
    </Combobox>
  );
}

Multiple

Allow users to select multiple options from the list.

import { useState } from "react";
import { Combobox, Text, Button } from "@cloudflare/kumo";

export function ComboboxMultipleDemo() {
  const [value, setValue] = useState<BotItem[]>([]);

  return (
    <div className="flex gap-2">
      <Combobox
        value={value}
        onValueChange={setValue}
        items={bots}
        isItemEqualToValue={(bot: BotItem, selected: BotItem) =>
          bot.value === selected.value
        }
        multiple
      >
        <Combobox.TriggerMultipleWithInput
          className="w-[400px]"
          placeholder="Select bots"
          renderItem={(selected: BotItem) => (
            <Combobox.Chip key={selected.value}>{selected.label}</Combobox.Chip>
          )}
          inputSide="right"
        />
        <Combobox.Content className="max-h-[200px] min-w-auto overflow-y-auto">
          <Combobox.Empty />
          <Combobox.List>
            {(item: BotItem) => (
              <Combobox.Item key={item.value} value={item}>
                <div className="flex gap-2">
                  <Text>{item.label}</Text>
                  <Text variant="secondary">{item.author}</Text>
                </div>
              </Combobox.Item>
            )}
          </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

import { useState } from "react";
import { Combobox } from "@cloudflare/kumo";

export function ComboboxWithFieldDemo() {
  const [value, setValue] = useState<DatabaseItem | null>(null);

  return (
    <div className="w-80">
      <Combobox
        items={databases}
        value={value}
        onValueChange={setValue}
        label="Database"
        description="Select your preferred database"
      >
        <Combobox.TriggerInput placeholder="Select database" />
        <Combobox.Content>
          <Combobox.Empty />
          <Combobox.List>
            {(item: DatabaseItem) => (
              <Combobox.Item key={item.value} value={item}>
                {item.label}
              </Combobox.Item>
            )}
          </Combobox.List>
        </Combobox.Content>
      </Combobox>
    </div>
  );
}

Disabled

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

import { Combobox } from "@cloudflare/kumo";

export function ComboboxDisabledDemo() {
  return (
    <div className="flex flex-wrap gap-4 items-start">
      <Combobox value="Apple" items={fruits} disabled>
        <Combobox.TriggerInput
          className="w-[200px]"
          placeholder="Select fruit"
        />
        <Combobox.Content>
          <Combobox.Empty />
          <Combobox.List>
            {(item: string) => (
              <Combobox.Item key={item} value={item}>
                {item}
              </Combobox.Item>
            )}
          </Combobox.List>
        </Combobox.Content>
      </Combobox>

      <Combobox value={languages[0]} items={languages} disabled>
        <Combobox.TriggerValue className="w-[200px]" />
        <Combobox.Content>
          <Combobox.Input placeholder="Search" />
          <Combobox.Empty />
          <Combobox.List>
            {(item: Language) => (
              <Combobox.Item key={item.value} value={item}>
                {item.emoji} {item.label}
              </Combobox.Item>
            )}
          </Combobox.List>
        </Combobox.Content>
      </Combobox>
    </div>
  );
}

Error State

Display validation errors with the error prop.

Please select a database
import { useState } from "react";
import { Combobox } from "@cloudflare/kumo";

export function ComboboxErrorDemo() {
  const [value, setValue] = useState<DatabaseItem | null>(null);

  return (
    <div className="w-80">
      <Combobox
        items={databases}
        value={value}
        onValueChange={setValue}
        label="Database"
        error={{ message: "Please select a database", match: true }}
      >
        <Combobox.TriggerInput placeholder="Select database" />
        <Combobox.Content>
          <Combobox.Empty />
          <Combobox.List>
            {(item: DatabaseItem) => (
              <Combobox.Item key={item.value} value={item}>
                {item.label}
              </Combobox.Item>
            )}
          </Combobox.List>
        </Combobox.Content>
      </Combobox>
    </div>
  );
}

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 className to Combobox.Content:

// Shorter dropdown (200px)
<Combobox.Content className="max-h-[200px]">

// Taller dropdown (500px)
<Combobox.Content className="max-h-[500px]">

// Use Tailwind presets
<Combobox.Content className="max-h-64">  // 256px
<Combobox.Content className="max-h-96">  // 384px (same as default)

API Reference

Combobox

Root component for the searchable select.

PropTypeDefaultDescription
size"xs" | "sm" | "base" | "lg""base"Size of the combobox trigger. Matches Input component sizes. - `"xs"` — Extra small for compact UIs (h-5 / 20px) - `"sm"` — Small for secondary fields (h-6.5 / 26px) - `"base"` — Default size (h-9 / 36px) - `"lg"` — Large for prominent fields (h-10 / 40px)
inputSide"right" | "top""right"Position of the text input relative to chips in multi-select mode. - `"right"` — Input inline to the right of chips - `"top"` — Input above chips
items*T[]-Array of items to display in the dropdown
valueT | T[]-Currently selected value(s)
childrenReactNode-Combobox content (trigger, content, items)
classNamestring-Additional CSS classes
labelReactNode-Label content for the combobox (enables Field wrapper) - can be a string or any React node
requiredboolean-Whether the combobox is required
labelTooltipReactNode-Tooltip content to display next to the label via an info icon
descriptionReactNode-Helper text displayed below the combobox
errorstring | object-Error message or validation error object
onValueChange(value: T | T[]) => void-Callback when selection changes
multipleboolean-Allow multiple selections
isItemEqualToValue(item: T, value: T) => boolean-Custom equality function for comparing items

Combobox.Content

Dropdown container for the list.

PropTypeDefault
classNamestring-
alignComboboxBase.Positioner.Props["align"]-
alignOffsetComboboxBase.Positioner.Props["alignOffset"]-
sideComboboxBase.Positioner.Props["side"]-
sideOffsetComboboxBase.Positioner.Props["sideOffset"]-

Combobox.Item

Individual selectable option.

PropTypeDefault

No component-specific props. Accepts standard HTML attributes.

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