---
layout: ~/layouts/MdxDocLayout.astro
title: "Combobox"
description: "A searchable select component that allows users to filter and select from a list of options."
sourceFile: "components/combobox"
baseUIComponent: "combobox"
---

import ComponentExample from "~/components/docs/ComponentExample.astro";
import ComponentSection from "~/components/docs/ComponentSection.astro";
import CodeBlock from "~/components/docs/CodeBlock.astro";
import Heading from "~/components/docs/Heading.astro";
import PropsTable from "~/components/docs/PropsTable.astro";
import {
  ComboboxDemo,
  ComboboxSearchableInsideDemo,
  ComboboxGroupedDemo,
  ComboboxMultipleDemo,
  ComboboxWithFieldDemo,
  ComboboxErrorDemo,
  ComboboxSizesDemo,
  ComboboxSizesSearchableInsideDemo,
} from "~/components/demos/ComboboxDemo";

<ComponentSection>
  <ComponentExample code={`function App() {
  const [value, setValue] = useState<string | null>("Apple");

return (

<Combobox value={value} onValueChange={(v) => setValue(v as any)} 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>  
 );
}

const fruits = [
"Apple",
"Orange",
"Banana",
"Grape",
"Strawberry",
"Blueberry",
"Cherry",
"Watermelon",
"Peach",
"Pear",
];`}>

<ComboboxDemo client:load />

  </ComponentExample>
</ComponentSection>

<ComponentSection>
  <Heading level={2}>Installation</Heading>
  <Heading level={3}>Barrel</Heading>
  <CodeBlock code={`import { Combobox } from "@cloudflare/kumo";`} lang="tsx" />
  <Heading level={3}>Granular</Heading>
  <CodeBlock
    code={`import { Combobox } from "@cloudflare/kumo/components/combobox";`}
    lang="tsx"
  />
</ComponentSection>

<ComponentSection>
  <Heading level={2}>Sizes</Heading>
  <p>
    The Combobox supports four size variants that match the Input component:
    `xs`,
    `sm`,
    `base` (default), and
    `lg`.
  </p>
  <ComponentExample
    code={`<Combobox size="sm" items={fruits} value={value} onValueChange={setValue}>
  <Combobox.TriggerInput placeholder="Select fruit" />
  <Combobox.Content>
    <Combobox.Empty />
    <Combobox.List>
      {(item) => (
        <Combobox.Item key={item} value={item}>
          {item}
        </Combobox.Item>
      )}
    </Combobox.List>
  </Combobox.Content>
</Combobox>`}
  >
    <ComboboxSizesDemo client:load />
  </ComponentExample>
  <p>
    Size also applies to `TriggerValue`
    (searchable inside variant):
  </p>
  <ComponentExample
    code={`<Combobox size="sm" items={languages} value={value} onValueChange={setValue}>
  <Combobox.TriggerValue className="w-[160px]" />
  <Combobox.Content>
    <Combobox.Input placeholder="Search" />
    <Combobox.Empty />
    <Combobox.List>
      {(item) => (
        <Combobox.Item key={item.value} value={item}>
          {item.emoji} {item.label}
        </Combobox.Item>
      )}
    </Combobox.List>
  </Combobox.Content>
</Combobox>`}
  >
    <ComboboxSizesSearchableInsideDemo client:load />
  </ComponentExample>
</ComponentSection>

<ComponentSection>
  <Heading level={2}>Searchable Item (Inside)</Heading>
  <p>A searchable select component inside popup that allows users to filter and select.</p>
  <ComponentExample code={`function App() {
  const [value, setValue] = useState<Language | null>(languages[0]);

return (

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

interface Language {
value: string;
label: string;
emoji: string;
}

const languages: Language[] = [
{ value: "en", label: "English", emoji: "🇬🇧" },
{ value: "fr", label: "French", emoji: "🇫🇷" },
{ value: "de", label: "German", emoji: "🇩🇪" },
{ value: "es", label: "Spanish", emoji: "🇪🇸" },
{ value: "it", label: "Italian", emoji: "🇮🇹" },
{ value: "pt", label: "Portuguese", emoji: "🇵🇹" },
];`}>

<ComboboxSearchableInsideDemo client:load />

  </ComponentExample>
</ComponentSection>

<ComponentSection>
  <Heading level={2}>Grouped</Heading>
  <p>Group items into categories using the Group and GroupLabel components.</p>
  <ComponentExample code={`function App() {
  const [value, setValue] = useState<string | null>("singapore");

return (

<Combobox value={value} onValueChange={(v) => setValue(v as any)} items={server}>
<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) => {
return (
<Combobox.Item key={item.value} value={item}>
{item.label}
</Combobox.Item>
);
}}
</Combobox.Collection>
</Combobox.Group>
)}
</Combobox.List>
</Combobox.Content>
</Combobox>  
 )
}

const server = [
{
value: "Asia",
items: [
{ label: "Japan", value: "japan" },
{ label: "China", value: "china" },
{ label: "Singapore", value: "singapore" },
],
},
{
value: "Europe",
items: [
{ label: "Germany", value: "germany" },
{ label: "France", value: "france" },
{ label: "Italy", value: "italy" },
],
},
];

interface ServerLocation {
label: string;
value: string;
}

interface ServerLocationGroup {
value: string;
items: ServerLocation[];
}`}>

<ComboboxGroupedDemo client:load />

  </ComponentExample>
</ComponentSection>

<ComponentSection>
  <Heading level={2}>Multiple</Heading>
  <p>Allow users to select multiple options from the list.</p>
  <ComponentExample
    code={`<Combobox
  value={value}
  onValueChange={setValue}
  items={bots}
  isItemEqualToValue={(bot, selected) => bot.value === selected.value}
  multiple
>
  <Combobox.TriggerMultipleWithInput
    className="w-[400px]"
    placeholder="Select bots"
    renderItem={(selected) => (
      <Combobox.Chip key={selected.value}>
        {selected.label}
      </Combobox.Chip>
    )}
    inputSide="right"
  />
  <Combobox.Content>
    <Combobox.Empty />
    <Combobox.List>
      {(item) => (
        <Combobox.Item key={item.value} value={item}>
          <Text>{item.label}</Text>
          <Text variant="secondary">{item.author}</Text>
        </Combobox.Item>
      )}
    </Combobox.List>
  </Combobox.Content>
</Combobox>`}
  >
    <ComboboxMultipleDemo client:load />
  </ComponentExample>
</ComponentSection>

<ComponentSection>
  <Heading level={2}>With Field</Heading>
  <p>Add label and description using the built-in Field wrapper.</p>
  <ComponentExample
    code={`<Combobox
  items={items}
  value={value}
  onValueChange={setValue}
  label="Database"
  description="Select your preferred database"
>
  <Combobox.TriggerInput placeholder="Select database" />
  <Combobox.Content>
    <Combobox.Empty />
    <Combobox.List>
      {(item) => (
        <Combobox.Item key={item.value} value={item}>
          {item.label}
        </Combobox.Item>
      )}
    </Combobox.List>
  </Combobox.Content>
</Combobox>`}
  >
    <ComboboxWithFieldDemo client:load />
  </ComponentExample>
</ComponentSection>

<ComponentSection>
  <Heading level={2}>Error State</Heading>
  <p>Display validation errors with the error prop.</p>
  <ComponentExample
    code={`<Combobox
  items={items}
  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) => (
        <Combobox.Item key={item.value} value={item}>
          {item.label}
        </Combobox.Item>
      )}
    </Combobox.List>
  </Combobox.Content>
</Combobox>`}
  >
    <ComboboxErrorDemo client:load />
  </ComponentExample>
</ComponentSection>

<ComponentSection>
  <Heading level={2}>Customizing Dropdown Height</Heading>
  <p>
    By default, <code class="text-surface">Combobox.Content</code> has a max height of <code class="text-surface">24rem</code> (384px) or the available viewport space, whichever is smaller. The dropdown scrolls automatically when content exceeds this height.
  </p>
  <p>
    To customize the max height, pass a className to <code class="text-surface">Combobox.Content</code>:
  </p>
  <CodeBlock code={`// 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)`} lang="tsx" />
</ComponentSection>

<ComponentSection>
  <Heading level={2}>API Reference</Heading>

<Heading level={3}>Combobox</Heading>
<p>Root component for the searchable select.</p>
<PropsTable component="Combobox" />

<Heading level={3}>Combobox.Content</Heading>
<p>Dropdown container for the list.</p>
<PropsTable component="Combobox.Content" />

<Heading level={3}>Combobox.Item</Heading>
<p>Individual selectable option.</p>
<PropsTable component="Combobox.Item" />

  <Heading level={3}>Additional Sub-components</Heading>
  <ul class="list-disc list-inside text-kumo-strong space-y-1">
    <li>`Combobox.TriggerInput` - Single-select input trigger</li>
    <li>`Combobox.TriggerValue` - Button trigger showing selected value</li>
    <li>`Combobox.TriggerMultipleWithInput` - Multi-select with chips</li>
    <li>`Combobox.Input` - Search input inside dropdown</li>
    <li>`Combobox.List` - List container with render prop</li>
    <li>`Combobox.Group` - Group container for categorized items</li>
    <li>`Combobox.GroupLabel` - Header label for a group</li>
    <li>`Combobox.Collection` - Items container within a group</li>
    <li>`Combobox.Chip` - Selected item chip</li>
    <li>`Combobox.Empty` - Empty state message</li>
  </ul>
</ComponentSection>
