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>
);
}安装
批量导入
import { Combobox } from "@cloudflare/kumo";按需导入
import { Combobox } from "@cloudflare/kumo/components/combobox";用法
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>
);
}示例
尺寸
Combobox 支持与 Input 组件一致的四种尺寸变体:xs、
sm、base(默认)和 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>
);
}尺寸同样适用于 TriggerValue(可搜索内嵌变体):
import { useState } from "react";
import { Combobox } from "@cloudflare/kumo";
import { languages, Language } from "./data/languages";
/** 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>
);
}可搜索条目(内嵌)
弹出层内嵌的可搜索选择组件,允许用户筛选和选择。
import { useState } from "react";
import { Combobox } from "@cloudflare/kumo";
import { languages, Language } from "./data/languages";
// 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>
);
}带占位符的可搜索选择
将 TriggerValue 与 placeholder 属性结合使用,即可创建可搜索的 Select 样式字段。
选中值之前会一直显示占位符。
import { useState } from "react";
import { Combobox } from "@cloudflare/kumo";
import { languages, Language } from "./data/languages";
/** Demonstrates using TriggerValue with a placeholder, behaving like a
* searchable Select field. The placeholder is shown until a value is selected. */
export function ComboboxSearchableSelectDemo() {
const [value, setValue] = useState<Language | null>(null);
return (
<Combobox
value={value}
onValueChange={(v) => setValue(v as Language | null)}
items={languages}
>
<Combobox.TriggerValue
className="w-[200px]"
placeholder="Select a language"
/>
<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>
);
}对象条目集合
使用 Combobox.createItems() 从应用对象派生出稳定的取值和标签。
import { useState } from "react";
import { Combobox } from "@cloudflare/kumo";
/** Demonstrates deriving Combobox values and labels from application objects. */
export function ComboboxCreateItemsDemo() {
const [value, setValue] = useState<DatabaseItem | null>(null);
return (
<Combobox
value={value}
onValueChange={(nextValue) => setValue(nextValue as DatabaseItem | null)}
items={databaseItems}
>
<Combobox.TriggerValue
className="w-[240px]"
placeholder="Select a database"
/>
<Combobox.Content>
<Combobox.Input placeholder="Search databases" />
<Combobox.Empty>No databases found.</Combobox.Empty>
<Combobox.List>
{(database: DatabaseItem) => (
<Combobox.Item key={database.value} value={database}>
{database.label}
</Combobox.Item>
)}
</Combobox.List>
</Combobox.Content>
</Combobox>
);
}自定义触发器
将 Combobox.Trigger 与 render 属性结合使用,
可以用你自己的元素替换默认的输入型触发器。配合
Combobox.Value 展示选中的值。适用于账号切换器、
侧边栏导航等默认外观无法满足的场景。
import { useState } from "react";
import { CaretUpDownIcon } from "@phosphor-icons/react";
import { Combobox, Button } from "@cloudflare/kumo";
import { languages, Language } from "./data/languages";
export function ComboboxCustomTriggerDemo() {
const [value, setValue] = useState<Language>(languages[0]);
return (
<Combobox
value={value}
onValueChange={(v) => setValue(v as Language)}
items={languages}
>
<Combobox.Trigger render={<Button variant="ghost" size="sm" />}>
<Combobox.Value>
<span className="truncate">
{value.emoji} {value.label}
</span>
</Combobox.Value>
<CaretUpDownIcon size={14} className="shrink-0 text-kumo-subtle" />
</Combobox.Trigger>
<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>
);
}分组
使用 Group 和 GroupLabel 组件将条目按类别分组。
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>
);
}多选
允许用户从列表中选择多个选项。
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>
);
}搭配 Field
使用内置的 Field 包装添加标签和描述。
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 属性可禁止交互。TriggerInput 和 TriggerValue 均支持。
import { Combobox } from "@cloudflare/kumo";
import { languages, Language } from "./data/languages";
export function ComboboxDisabledDemo() {
return (
<div className="flex flex-wrap items-start gap-4">
<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>
);
}禁用条目
为单个 Combobox.Item 传入 disabled 属性,可使其不可选择。禁用行以弱化样式渲染,并在键盘导航选择时被跳过。
import { useState } from "react";
import { Combobox, Text } from "@cloudflare/kumo";
/** Demonstrates disabled individual items. The `disabled` prop on
* `Combobox.Item` blocks click and keyboard selection, and renders the row
* with muted text + a not-allowed cursor. Useful for surfacing options that
* exist but the user can't pick (e.g. permission-gated, read-only, or
* already in use elsewhere). */
export function ComboboxDisabledItemsDemo() {
type DatabaseItemWithDisabled = DatabaseItem & {
disabled?: boolean;
reason?: string;
};
const items: DatabaseItemWithDisabled[] = [
{ 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",
},
{ value: "redis", label: "Redis" },
{ value: "d1", label: "Cloudflare D1" },
];
const [value, setValue] = useState<DatabaseItemWithDisabled | null>(null);
return (
<div className="w-80">
<Combobox value={value} onValueChange={setValue} items={items}>
<Combobox.TriggerInput placeholder="Select database" />
<Combobox.Content>
<Combobox.Empty />
<Combobox.List>
{(item: DatabaseItemWithDisabled) => (
<Combobox.Item
key={item.value}
value={item}
disabled={item.disabled}
>
<span>
{item.label}
{item.reason && (
<Text variant="secondary" size="xs" as="span">
{" — "}
{item.reason}
</Text>
)}
</span>
</Combobox.Item>
)}
</Combobox.List>
</Combobox.Content>
</Combobox>
</div>
);
}错误状态
使用 error 属性展示校验错误。
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>
);
}过滤
过滤默认不区分大小写和重音,底层由
Intl.Collator 驱动。对于字符串条目,无需自定义
filter。
当需要按对象条目的属性过滤时,使用 Combobox.useFilter() 来
保留内置的重音不敏感匹配:
function LanguagePicker() {
const { contains } = Combobox.useFilter();
const filter = useCallback(
(item: Language, query: string) => contains(item.label, query),
[contains],
);
return (
<Combobox items={languages} filter={filter}>
{/* ... */}
</Combobox>
);
}如需完全禁用过滤(例如结果来自服务器),可传
filter={null}:
<Combobox items={results} filter={null}>
...
</Combobox>对象条目集合
当条目是应用对象而非原始值时,使用 Combobox.createItems()。
它会派生稳定的取值和标签,同时让列表渲染器仍能拿到源对象。
import { useState } from "react";
import { Combobox } from "@cloudflare/kumo";
type Fruit = { id: string; label: string };
const fruits = Combobox.createItems<Fruit>(
[
{ id: "apple", label: "Apple" },
{ id: "banana", label: "Banana" },
],
{
getValue: (fruit) => fruit.id,
getLabel: (fruit) => fruit.label,
},
);
function FruitPicker() {
const [value, setValue] = useState<Fruit | null>(null);
return (
<Combobox items={fruits} value={value} onValueChange={setValue}>
<Combobox.TriggerInput placeholder="Select a fruit" />
<Combobox.Content>
<Combobox.List>
{(fruit) => (
<Combobox.Item key={fruit.id} value={fruit}>
{fruit.label}
</Combobox.Item>
)}
</Combobox.List>
</Combobox.Content>
</Combobox>
);
}在模块作用域创建静态集合。对于运行时变化的数据,
以源数据为依赖对 Combobox.createItems() 做 memoize(记忆化)。
自定义下拉高度
默认情况下,Combobox.Content 的最大高度为 24rem(384px),
或可用视口空间,取两者中的较小值。当内容超出该高度时,
下拉列表会自动滚动。
如需自定义最大高度,请向 Combobox.Content 传入 className:
// 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 参考
Combobox
可搜索选择组件的根组件。
| Prop | Type | Default | Description |
|---|---|---|---|
| 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 |
| value | T | T[] | - | Currently selected value(s) |
| children | ReactNode | - | Combobox content (trigger, content, items) |
| className | string | - | Additional CSS classes |
| label | ReactNode | - | Label content for the combobox (enables Field wrapper) - can be a string or any React node |
| required | boolean | - | Whether the combobox is required |
| labelTooltip | ReactNode | - | Tooltip content to display next to the label via an info icon |
| description | ReactNode | - | Helper text displayed below the combobox |
| error | string | object | - | Error message or validation error object |
| onValueChange | (value: T | T[]) => void | - | Callback when selection changes |
| multiple | boolean | - | Allow multiple selections |
| isItemEqualToValue | (item: T, value: T) => boolean | - | Custom equality function for comparing items |
Combobox.Content
列表的下拉容器。
| Prop | Type | Default |
|---|---|---|
| className | string | - |
| align | ComboboxBase.Positioner.Props["align"] | - |
| alignOffset | ComboboxBase.Positioner.Props["alignOffset"] | - |
| side | ComboboxBase.Positioner.Props["side"] | - |
| sideOffset | ComboboxBase.Positioner.Props["sideOffset"] | - |
| anchor | ComboboxBase.Positioner.Props["anchor"] | - |
| positionMethod | ComboboxBase.Positioner.Props["positionMethod"] | - |
| collisionAvoidance | ComboboxBase.Positioner.Props["collisionAvoidance"] | - |
| collisionBoundary | ComboboxBase.Positioner.Props["collisionBoundary"] | - |
| collisionPadding | ComboboxBase.Positioner.Props["collisionPadding"] | - |
| sticky | ComboboxBase.Positioner.Props["sticky"] | - |
| disableAnchorTracking | ComboboxBase.Positioner.Props["disableAnchorTracking"] | - |
| container | PortalContainer | - |
Combobox.Item
单个可选择的选项。
| Prop | Type | Default |
|---|
No component-specific props. Accepts standard HTML attributes.
其他子组件
Combobox.TriggerInput- 单选输入触发器Combobox.TriggerValue- 显示选中值的按钮触发器Combobox.TriggerMultipleWithInput- 带 chip 标签的多选触发器Combobox.Input- 下拉内的搜索输入框Combobox.List- 带 render 属性的列表容器Combobox.Group- 分类条目的分组容器Combobox.GroupLabel- 分组的标题标签Combobox.Collection- 组内的条目容器Combobox.Chip- 选中条目的 chip 标签Combobox.Empty- 空状态提示Combobox.createItems- 对象条目的集合辅助方法