import { DropdownMenu, Button } from "@cloudflare/kumo";
import { PlusIcon } from "@phosphor-icons/react";
export function DropdownBasicDemo() {
return (
<DropdownMenu>
<DropdownMenu.Trigger render={<Button icon={PlusIcon}>Add</Button>} />
<DropdownMenu.Content>
<DropdownMenu.Item>Worker</DropdownMenu.Item>
<DropdownMenu.Item>Pages</DropdownMenu.Item>
<DropdownMenu.Item>KV Namespace</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu>
);
}安装
批量导入
import { DropdownMenu } from "@cloudflare/kumo";按需导入
import { DropdownMenu } from "@cloudflare/kumo/components/dropdown";用法
import { DropdownMenu, Button } from "@cloudflare/kumo";
export default function Example() {
return (
<DropdownMenu>
<DropdownMenu.Trigger render={<Button>Menu</Button>} />
<DropdownMenu.Content>
<DropdownMenu.Item onClick={() => console.log("edit")}>
Edit
</DropdownMenu.Item>
<DropdownMenu.Item onClick={() => console.log("duplicate")}>
Duplicate
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item
variant="danger"
onClick={() => console.log("delete")}
>
Delete
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu>
);
}示例
基本下拉
import { DropdownMenu, Button } from "@cloudflare/kumo";
import { PlusIcon } from "@phosphor-icons/react";
export function DropdownBasicDemo() {
return (
<DropdownMenu>
<DropdownMenu.Trigger render={<Button icon={PlusIcon}>Add</Button>} />
<DropdownMenu.Content>
<DropdownMenu.Item>Worker</DropdownMenu.Item>
<DropdownMenu.Item>Pages</DropdownMenu.Item>
<DropdownMenu.Item>KV Namespace</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu>
);
}缩进条目
当菜单中的任一条目带有图标时,没有图标的条目(包括标签)会自动缩进,
使它们的文本对齐。当自动检测无法看到图标时(例如条目通过 render
渲染自己的内容),请使用 inset 强制缩进。
import { DropdownMenu, Button } from "@cloudflare/kumo";
import { CopyIcon, PencilSimpleIcon, TrashIcon } from "@phosphor-icons/react";
/**
* Items without an icon are inset automatically when another item in the same
* menu has one, so their text lines up.
*/
export function DropdownInsetDemo() {
return (
<DropdownMenu>
<DropdownMenu.Trigger render={<Button>Edit</Button>} />
<DropdownMenu.Content>
<DropdownMenu.Item icon={PencilSimpleIcon}>Rename</DropdownMenu.Item>
<DropdownMenu.Item icon={CopyIcon}>Duplicate</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item>Move to folder</DropdownMenu.Item>
<DropdownMenu.Item>Add to favorites</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item icon={TrashIcon} variant="danger">
Delete
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu>
);
}处理条目点击
使用 DropdownMenu.Item 上的 onClick 处理操作。每个条目都会接收到标准的 React 鼠标事件处理器。
import { useState } from "react";
import { DropdownMenu, Button } from "@cloudflare/kumo";
import { ArrowsClockwiseIcon, CopyIcon, PencilSimpleIcon, TrashIcon } from "@phosphor-icons/react";
/**
* Use `onClick` on `DropdownMenu.Item` to handle item actions.
* Each item receives a standard React mouse event handler.
*/
export function DropdownOnClickDemo() {
const [lastAction, setLastAction] = useState<string | null>(null);
return (
<div className="flex flex-col items-start gap-2">
<DropdownMenu>
<DropdownMenu.Trigger render={<Button>Actions</Button>} />
<DropdownMenu.Content>
<DropdownMenu.Item
icon={CopyIcon}
onClick={() => setLastAction("Duplicated")}
>
Duplicate
</DropdownMenu.Item>
<DropdownMenu.Item
icon={PencilSimpleIcon}
onClick={() => setLastAction("Renamed")}
>
Rename
</DropdownMenu.Item>
<DropdownMenu.Item
icon={<ArrowsClockwiseIcon className="animate-spin" />}
onClick={() => setLastAction("Refreshed")}
>
Refresh
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item
icon={TrashIcon}
variant="danger"
onClick={() => setLastAction("Deleted")}
>
Delete
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu>
{lastAction && (
<p className="text-sm text-kumo-subtle">
Last action: <span className="text-kumo-default">{lastAction}</span>
</p>
)}
</div>
);
}复选框条目
使用 DropdownMenu.CheckboxItem 实现可独立勾选或取消勾选的可切换选项。
import { useState } from "react";
import { DropdownMenu, Button } from "@cloudflare/kumo";
export function DropdownCheckboxDemo() {
const [showSidebar, setShowSidebar] = useState(true);
const [showLineNumbers, setShowLineNumbers] = useState(false);
const [wordWrap, setWordWrap] = useState(true);
return (
<DropdownMenu>
<DropdownMenu.Trigger render={<Button>View Options</Button>} />
<DropdownMenu.Content>
<DropdownMenu.Group>
<DropdownMenu.Label>Display</DropdownMenu.Label>
<DropdownMenu.CheckboxItem
checked={showSidebar}
onCheckedChange={setShowSidebar}
>
Show sidebar
</DropdownMenu.CheckboxItem>
<DropdownMenu.CheckboxItem
checked={showLineNumbers}
onCheckedChange={setShowLineNumbers}
>
Show line numbers
</DropdownMenu.CheckboxItem>
<DropdownMenu.CheckboxItem
checked={wordWrap}
onCheckedChange={setWordWrap}
>
Word wrap
</DropdownMenu.CheckboxItem>
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu>
);
}带单选的内嵌菜单
使用 DropdownMenu.Sub、DropdownMenu.SubTrigger 和
DropdownMenu.SubContent 创建嵌套子菜单。对于语言、时区等单选列表,
请使用 DropdownMenu.RadioGroup 和
DropdownMenu.RadioItem。
import { useState } from "react";
import { DropdownMenu, Button } from "@cloudflare/kumo";
import { UserIcon, CreditCardIcon, MoonIcon, SignOutIcon, GlobeIcon, ClockIcon } from "@phosphor-icons/react";
export function DropdownNestedDemo() {
const [language, setLanguage] = useState("en");
const [timezone, setTimezone] = useState("America/Los_Angeles");
return (
<DropdownMenu>
<DropdownMenu.Trigger render={<Button icon={UserIcon}>Account</Button>} />
<DropdownMenu.Content>
<DropdownMenu.Item icon={UserIcon}>Profile</DropdownMenu.Item>
<DropdownMenu.Item icon={CreditCardIcon}>Billing</DropdownMenu.Item>
<DropdownMenu.Item icon={MoonIcon}>Dark mode</DropdownMenu.Item>
{/* Language submenu with RadioGroup */}
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger icon={GlobeIcon}>
Language
</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent>
<DropdownMenu.Group>
<DropdownMenu.RadioGroup
value={language}
onValueChange={setLanguage}
>
{languages.map((lang) => (
<DropdownMenu.RadioItem key={lang.code} value={lang.code}>
{lang.label}
<DropdownMenu.RadioItemIndicator />
</DropdownMenu.RadioItem>
))}
</DropdownMenu.RadioGroup>
</DropdownMenu.Group>
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
{/* Timezone submenu with RadioGroup */}
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger icon={<ClockIcon weight="duotone" />}>
Set Timezone
</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent>
<DropdownMenu.Group>
<DropdownMenu.RadioGroup
value={timezone}
onValueChange={setTimezone}
>
{timezones.map((tz) => (
<DropdownMenu.RadioItem key={tz.value} value={tz.value}>
{tz.label}
<DropdownMenu.RadioItemIndicator />
</DropdownMenu.RadioItem>
))}
</DropdownMenu.RadioGroup>
</DropdownMenu.Group>
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
<DropdownMenu.Separator />
<DropdownMenu.Item icon={SignOutIcon} variant="danger">
Log out
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu>
);
}带头像的自定义触发器
使用 render 属性自定义触发器元素,同时传入要渲染在其中的子内容。
当需要将头像等非按钮触发器包裹在无障碍按钮元素中时,此功能非常实用。
import { DropdownMenu } from "@cloudflare/kumo";
import { UserIcon, SignOutIcon, GearIcon } from "@phosphor-icons/react";
/**
* Demonstrates using the render prop with children to compose a custom trigger
* that contains other elements. The render prop provides the trigger element,
* while children are rendered inside it.
*/
export function DropdownAvatarTriggerDemo() {
return (
<DropdownMenu>
<DropdownMenu.Trigger
render={<button type="button" className="rounded-full" />}
>
<span className="flex h-8 w-8 items-center justify-center rounded-full bg-kumo-brand text-sm font-medium text-white">
MR
</span>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Item icon={UserIcon}>Profile</DropdownMenu.Item>
<DropdownMenu.Item icon={GearIcon}>Settings</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item icon={SignOutIcon} variant="danger">
Log out
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu>
);
}导航链接
使用 DropdownMenu.LinkItem 处理导航到 URL 的菜单条目。
它会渲染语义化的 <a> 元素,可完全控制 target、rel 等链接属性。
import { DropdownMenu, Button } from "@cloudflare/kumo";
import { GearIcon, BookOpenIcon, ArrowSquareOutIcon } from "@phosphor-icons/react";
/**
* Demonstrates the new LinkItem component for navigation links.
* Use LinkItem instead of Item with href for cleaner, more semantic links.
*/
export function DropdownLinkItemDemo() {
return (
<DropdownMenu>
<DropdownMenu.Trigger render={<Button>Resources</Button>} />
<DropdownMenu.Content>
<DropdownMenu.LinkItem href="/settings" icon={GearIcon}>
Settings
</DropdownMenu.LinkItem>
<DropdownMenu.LinkItem href="/docs" icon={BookOpenIcon}>
Documentation
</DropdownMenu.LinkItem>
<DropdownMenu.Separator />
<DropdownMenu.LinkItem
href="https://developers.cloudflare.com"
target="_blank"
icon={ArrowSquareOutIcon}
>
Developer Docs
</DropdownMenu.LinkItem>
</DropdownMenu.Content>
</DropdownMenu>
);
}长列表
当下拉包含大量条目时,内容会自动将高度限制为可用的视口空间, 并变为可滚动。
import { DropdownMenu, Button } from "@cloudflare/kumo";
/**
* A dropdown with a very long list of items to demonstrate the max-height
* behavior — the content scrolls when it exceeds available viewport space.
*/
export function DropdownLongListDemo() {
const items = Array.from({ length: 30 }, (_, i) => `Option ${i + 1}`);
return (
<DropdownMenu>
<DropdownMenu.Trigger render={<Button>Open long list</Button>} />
<DropdownMenu.Content>
{items.map((item) => (
<DropdownMenu.Item key={item}>{item}</DropdownMenu.Item>
))}
</DropdownMenu.Content>
</DropdownMenu>
);
}API 参考
DropdownMenu
管理下拉状态的主组件。
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "default" | "danger" | "default" | 下拉项的视觉样式。
|
DropdownMenu.Trigger
点击即可打开下拉的按钮。
| Prop | Type | Default |
|---|
No component-specific props. Accepts standard HTML attributes.
DropdownMenu.Content
菜单弹出层。它组合了 Portal、Positioner 和 Popup:诸如 side、
align、sideOffset 和 alignOffset 之类的定位属性传递给
Positioner,className 传递给 Popup。
| Prop | Type | Default | Description |
|---|---|---|---|
| sideOffset | number | 8 | 与触发器的像素距离。 |
| container | PortalContainer | - | Portal 的容器元素。用于在 Shadow DOM 或自定义容器内渲染。 |
DropdownMenu.Portal, Positioner and Popup
组成 Content 的各部分。当你需要分别对 Positioner 和 Popup 进行样式
调整或配置时,可直接组合它们。Portal 遵循 KumoPortalProvider,
Popup 承载菜单表面样式和打开/关闭动画。
<DropdownMenu.Portal>
<DropdownMenu.Positioner side="top" align="end">
<DropdownMenu.Popup className="min-w-56">
<DropdownMenu.Item>Edit</DropdownMenu.Item>
</DropdownMenu.Popup>
</DropdownMenu.Positioner>
</DropdownMenu.Portal>两者接受与 Base UI 的 Menu.Positioner 和 Menu.Popup 相同的属性。
完整列表请参阅 Base UI Menu 文档。
DropdownMenu.Item
用于操作的单个菜单条目。
| Prop | Type | Default | Description |
|---|---|---|---|
| icon | Icon | ReactNode | - | 标签前显示的图标。 |
| variant | "default" | "danger" | "default" | 项目的视觉样式。 |
| selected | boolean | - | 当为 |
| inset | boolean | - | 添加左侧内边距,以与带图标的项目对齐。 |
| onClick | (event: React.MouseEvent) => void | - | 项目被点击时的回调。 |
| closeOnClick | boolean | true | 点击此项后菜单是否关闭。 |
| disabled | boolean | - | 当为 |
DropdownMenu.LinkItem
导航到 URL 的菜单条目。渲染语义化的 <a> 元素。导航链接请使用此组件,而不是 Item。
| Prop | Type | Default | Description |
|---|---|---|---|
| href | string | - | 点击时要导航到的 URL。 |
| icon | Icon | ReactNode | - | 标签前显示的图标。 |
| variant | "default" | "danger" | "default" | 项目的视觉样式。 |
| inset | boolean | - | 添加左侧内边距,以与带图标的项目对齐。 |
| target | string | - | 链接目标属性(例如新标签页使用 "_blank")。 |
| render | ReactElement | ((props, state) => ReactElement) | - | 自定义渲染为链接的元素。用于与框架路由器集成(例如 Next.js Link)。 |
DropdownMenu.CheckboxItem
可开启或关闭的菜单条目。适用于独立的布尔选项。
| Prop | Type | Default | Description |
|---|---|---|---|
| checked | boolean | - | 该项是否被选中。 |
| defaultChecked | boolean | false | 该项初始是否被选中(非受控)。 |
| onCheckedChange | (checked: boolean, event: ChangeEventDetails) => void | - | 选中状态变化时的回调。 |
| closeOnClick | boolean | false | 点击此项后菜单是否关闭。 |
| disabled | boolean | - | 当为 |
DropdownMenu.Sub
嵌套子菜单的主组件。将 SubTrigger 和 SubContent 包裹在此组件内。
| Prop | Type | Default |
|---|
No component-specific props. Accepts standard HTML attributes.
DropdownMenu.SubTrigger
悬停或点击时打开嵌套子菜单的菜单条目。自动显示插入标记(caret)图标,
并在其子菜单打开时保持高亮。与 Item 一样,其 icon 属性接受 Phosphor
图标组件或渲染的元素。
| Prop | Type | Default | Description |
|---|---|---|---|
| icon | Icon | - | 标签前显示的图标。 |
| inset | boolean | - | 添加左侧内边距,以与带图标的项目对齐。 |
DropdownMenu.SubContent
子菜单条目的容器。相对于 SubTrigger 定位。当子菜单位于触发器旁边时,
它会取消弹出层的内边距,使第一个条目与触发器对齐,并稍微与父菜单重叠。
如果没有足够的空间而移动到触发器的上方或下方,则使用与顶级菜单相同的
间距。传入 sideOffset 或 alignOffset(一个数字或已解析 side 的函数)
进行覆盖。
| Prop | Type | Default |
|---|
No component-specific props. Accepts standard HTML attributes.
DropdownMenu.Separator
菜单条目之间的视觉分隔线。
| Prop | Type | Default |
|---|
No component-specific props. Accepts standard HTML attributes.
DropdownMenu.RadioGroup
对单选条目分组以实现单选行为。一次只能选中一个条目。
| Prop | Type | Default | Description |
|---|---|---|---|
| value | any | - | 当前选中单选项目的受控值。 |
| defaultValue | any | - | 初始选中的值(非受控)。 |
| onValueChange | (value: any, event: ChangeEventDetails) => void | - | 选中值发生变化时的回调。 |
| disabled | boolean | - | 当 true 时,组中所有单选项目均被禁用。 |
DropdownMenu.RadioItem
行为类似单选按钮的菜单条目。必须在 RadioGroup 内使用。
| Prop | Type | Default | Description |
|---|---|---|---|
| value* | any | - | 此单选项目的值。 |
| icon | Icon | ReactNode | - | 标签前显示的图标。 |
| inset | boolean | - | 添加左侧内边距,以与带图标的项目对齐。 |
| closeOnClick | boolean | false | 点击此项后菜单是否关闭。 |
| disabled | boolean | - | 当为 |
DropdownMenu.RadioItemIndicator
显示 RadioItem 的选中状态。默认显示对勾。
| Prop | Type | Default |
|---|
No component-specific props. Accepts standard HTML attributes.