Dropdown Menu 下拉菜单
@lark-ui/core
import { DropdownMenu, Button } from "@lark-ui/core";
import { PlusIcon } from "@phosphor-icons/react";

export function DropdownBasicDemo() {
  return (
    <DropdownMenu>
      <DropdownMenu.Trigger render={<Button icon={PlusIcon}>添加</Button>} />
      <DropdownMenu.Content>
        <DropdownMenu.Item>Worker</DropdownMenu.Item>
        <DropdownMenu.Item>Pages</DropdownMenu.Item>
        <DropdownMenu.Item>KV 命名空间</DropdownMenu.Item>
      </DropdownMenu.Content>
    </DropdownMenu>
  );
}

安装

统一导出

import { DropdownMenu } from "@lark-ui/core";

按需导出

import { DropdownMenu } from "@lark-ui/core/components/dropdown";

用法

import { DropdownMenu, Button } from "@lark-ui/core";

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 "@lark-ui/core";
import { PlusIcon } from "@phosphor-icons/react";

export function DropdownBasicDemo() {
  return (
    <DropdownMenu>
      <DropdownMenu.Trigger render={<Button icon={PlusIcon}>添加</Button>} />
      <DropdownMenu.Content>
        <DropdownMenu.Item>Worker</DropdownMenu.Item>
        <DropdownMenu.Item>Pages</DropdownMenu.Item>
        <DropdownMenu.Item>KV 命名空间</DropdownMenu.Item>
      </DropdownMenu.Content>
    </DropdownMenu>
  );
}

缩进对齐的菜单项

当菜单中任意一项带有图标时,没有图标的项(包括标签)会自动缩进对齐,使文本排版整齐。当自动检测无法识别图标时(例如菜单项通过 render 渲染自己的内容),可使用 inset 强制缩进对齐。

import { DropdownMenu, Button } from "@lark-ui/core";
import { CopyIcon, PencilSimpleIcon, TrashIcon } from "@phosphor-icons/react";

/**
 * 当同一菜单中的其他条目带有图标时,无图标的条目会自动缩进,
 * 使文字对齐。
 */
export function DropdownInsetDemo() {
  return (
    <DropdownMenu>
      <DropdownMenu.Trigger render={<Button>编辑</Button>} />
      <DropdownMenu.Content>
        <DropdownMenu.Item icon={PencilSimpleIcon}>重命名</DropdownMenu.Item>
        <DropdownMenu.Item icon={CopyIcon}>创建副本</DropdownMenu.Item>
        <DropdownMenu.Separator />
        <DropdownMenu.Item>移动到文件夹</DropdownMenu.Item>
        <DropdownMenu.Item>添加到收藏夹</DropdownMenu.Item>
        <DropdownMenu.Separator />
        <DropdownMenu.Item icon={TrashIcon} variant="danger">
          删除
        </DropdownMenu.Item>
      </DropdownMenu.Content>
    </DropdownMenu>
  );
}

处理菜单项点击

在 DropdownMenu.Item 上使用 onClick 处理操作。每个菜单项都会接收到标准的 React 鼠标事件处理器。

import { useState } from "react";
import { DropdownMenu, Button } from "@lark-ui/core";
import { ArrowsClockwiseIcon, CopyIcon, PencilSimpleIcon, TrashIcon } from "@phosphor-icons/react";

/**
 * 在 `DropdownMenu.Item` 上使用 `onClick` 处理条目操作。
 * 每个条目都会收到标准的 React 鼠标事件处理函数。
 */
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>操作</Button>} />
        <DropdownMenu.Content>
          <DropdownMenu.Item
            icon={CopyIcon}
            onClick={() => setLastAction("已创建副本")}
          >
            创建副本
          </DropdownMenu.Item>
          <DropdownMenu.Item
            icon={PencilSimpleIcon}
            onClick={() => setLastAction("已重命名")}
          >
            重命名
          </DropdownMenu.Item>
          <DropdownMenu.Item
            icon={<ArrowsClockwiseIcon className="animate-spin" />}
            onClick={() => setLastAction("已刷新")}
          >
            刷新
          </DropdownMenu.Item>
          <DropdownMenu.Separator />
          <DropdownMenu.Item
            icon={TrashIcon}
            variant="danger"
            onClick={() => setLastAction("已删除")}
          >
            删除
          </DropdownMenu.Item>
        </DropdownMenu.Content>
      </DropdownMenu>
      {lastAction && (
        <p className="text-sm text-lark-subtle">
          最近操作:<span className="text-lark-default">{lastAction}</span>
        </p>
      )}
    </div>
  );
}

复选框菜单项

使用 DropdownMenu.CheckboxItem 表示可切换、可独立勾选或取消勾选的选项。

import { useState } from "react";
import { DropdownMenu, Button } from "@lark-ui/core";

export function DropdownCheckboxDemo() {
  const [showSidebar, setShowSidebar] = useState(true);
  const [showLineNumbers, setShowLineNumbers] = useState(false);
  const [wordWrap, setWordWrap] = useState(true);

  return (
    <DropdownMenu>
      <DropdownMenu.Trigger render={<Button>查看选项</Button>} />
      <DropdownMenu.Content>
        <DropdownMenu.Group>
          <DropdownMenu.Label>显示</DropdownMenu.Label>
          <DropdownMenu.CheckboxItem
            checked={showSidebar}
            onCheckedChange={setShowSidebar}
          >
            显示侧边栏
          </DropdownMenu.CheckboxItem>
          <DropdownMenu.CheckboxItem
            checked={showLineNumbers}
            onCheckedChange={setShowLineNumbers}
          >
            显示行号
          </DropdownMenu.CheckboxItem>
          <DropdownMenu.CheckboxItem
            checked={wordWrap}
            onCheckedChange={setWordWrap}
          >
            自动换行
          </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 "@lark-ui/core";
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}>账户</Button>} />
      <DropdownMenu.Content>
        <DropdownMenu.Item icon={UserIcon}>个人资料</DropdownMenu.Item>
        <DropdownMenu.Item icon={CreditCardIcon}>账单</DropdownMenu.Item>
        <DropdownMenu.Item icon={MoonIcon}>深色模式</DropdownMenu.Item>

        {/* Language submenu with RadioGroup */}
        <DropdownMenu.Sub>
          <DropdownMenu.SubTrigger icon={GlobeIcon}>
            语言
          </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" />}>
            设置时区
          </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">
          退出登录
        </DropdownMenu.Item>
      </DropdownMenu.Content>
    </DropdownMenu>
  );
}

使用头像的自定义触发器

使用 render 属性自定义触发元素,同时把子元素渲染到其内部。当触发器不是按钮(例如头像),但又需要包裹在具备无障碍语义的按钮元素中时,这非常有用。

import { DropdownMenu } from "@lark-ui/core";
import { UserIcon, SignOutIcon, GearIcon } from "@phosphor-icons/react";

/**
 * 演示结合 render 属性与 children 组合出包含其他元素的自定义触发器。
 * render 属性提供触发器元素,children 则渲染在其内部。
 */
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-lark-brand text-sm font-medium text-white">
          MR
        </span>
      </DropdownMenu.Trigger>
      <DropdownMenu.Content>
        <DropdownMenu.Item icon={UserIcon}>个人资料</DropdownMenu.Item>
        <DropdownMenu.Item icon={GearIcon}>设置</DropdownMenu.Item>
        <DropdownMenu.Separator />
        <DropdownMenu.Item icon={SignOutIcon} variant="danger">
          退出登录
        </DropdownMenu.Item>
      </DropdownMenu.Content>
    </DropdownMenu>
  );
}

导航链接

对于跳转到 URL 的菜单项,请使用 DropdownMenu.LinkItem。它会渲染语义化的 <a> 元素,并完整支持 target、rel 等链接属性。

import { DropdownMenu, Button } from "@lark-ui/core";
import { GearIcon, BookOpenIcon, ArrowSquareOutIcon } from "@phosphor-icons/react";

/**
 * 演示用于导航链接的 LinkItem 组件。
 * 使用带 href 的 Item 时改用 LinkItem,可获得更简洁、语义更明确的链接。
 */
export function DropdownLinkItemDemo() {
  return (
    <DropdownMenu>
      <DropdownMenu.Trigger render={<Button>资源</Button>} />
      <DropdownMenu.Content>
        <DropdownMenu.LinkItem href="/settings" icon={GearIcon}>
          设置
        </DropdownMenu.LinkItem>
        <DropdownMenu.LinkItem href="/docs" icon={BookOpenIcon}>
          文档
        </DropdownMenu.LinkItem>
        <DropdownMenu.Separator />
        <DropdownMenu.LinkItem
          href="https://developers.cloudflare.com"
          target="_blank"
          icon={ArrowSquareOutIcon}
        >
          开发者文档
        </DropdownMenu.LinkItem>
      </DropdownMenu.Content>
    </DropdownMenu>
  );
}

长列表

当下拉菜单包含大量菜单项时,内容会自动将高度限制在可用的视口空间内,并支持滚动。

import { DropdownMenu, Button } from "@lark-ui/core";

/**
 * 包含超长条目列表的下拉菜单,用于演示最大高度行为——当内容超出可用视口空间时
 * 会滚动显示。
 */
export function DropdownLongListDemo() {
  const items = Array.from({ length: 30 }, (_, i) => `选项 ${i + 1}`);

  return (
    <DropdownMenu>
      <DropdownMenu.Trigger render={<Button>打开长列表</Button>} />
      <DropdownMenu.Content>
        {items.map((item) => (
          <DropdownMenu.Item key={item}>{item}</DropdownMenu.Item>
        ))}
      </DropdownMenu.Content>
    </DropdownMenu>
  );
}

API 参考

管理下拉菜单状态的根组件。

属性类型默认值说明
variant"default" | "danger""default"下拉菜单项的视觉样式。 - `"default"` — 标准菜单项外观 - `"danger"` — 红色文字的危险操作

点击时打开下拉菜单的按钮。

属性类型默认值

该组件没有专有属性,支持标准 HTML 属性。

菜单弹层。由 Portal、Positioner 和 Popup 组合而成:side、align、sideOffset、alignOffset 等定位属性作用于 Positioner,className 作用于 Popup。

属性类型默认值说明
sideOffsetnumber8与触发元素之间的距离(像素)。
containerPortalContainer-传送层的容器元素。可用于在 Shadow DOM 或自定义容器内渲染。

组成 Content 的各个部件。当你需要分别设置 Positioner 和 Popup 的样式或配置时,可以直接组合它们。Portal 会遵循 LarkPortalProvider 的设置,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 文档。

用于执行操作的单个菜单项。

属性类型默认值说明
iconIcon | ReactNode-显示在标签前的图标。
variant"default" | "danger""default"菜单项的视觉样式。
selectedboolean-为 true 时显示勾选标记。
insetboolean-增加左侧内边距,以便与带图标的菜单项对齐。
onClick(event: React.MouseEvent) => void-点击菜单项时的回调。
closeOnClickbooleantrue点击该菜单项后菜单是否关闭。
disabledboolean-为 true 时该菜单项无法交互。

跳转到 URL 的菜单项。渲染语义化的 <a> 元素。导航链接请使用它而不是 Item。

属性类型默认值说明
hrefstring-点击后跳转的 URL。
iconIcon | ReactNode-显示在标签前的图标。
variant"default" | "danger""default"菜单项的视觉样式。
insetboolean-增加左侧内边距,以便与带图标的菜单项对齐。
targetstring-链接的 target 属性(例如用 “_blank” 在新标签页打开)。
renderReactElement | ((props, state) => ReactElement)-渲染为链接的自定义元素。用于接入框架路由(例如 Next.js 的 Link)。

可切换开关状态的菜单项。适用于相互独立的布尔选项。

属性类型默认值说明
checkedboolean-该菜单项是否选中。
defaultCheckedbooleanfalse该菜单项初始是否选中(非受控)。
onCheckedChange(checked: boolean, event: ChangeEventDetails) => void-选中状态变化时的回调。
closeOnClickbooleanfalse点击该菜单项后菜单是否关闭。
disabledboolean-为 true 时该菜单项无法交互。

嵌套子菜单的根组件。请将 SubTrigger 和 SubContent 包裹在其中。

属性类型默认值

该组件没有专有属性,支持标准 HTML 属性。

悬停或点击时打开嵌套子菜单的菜单项。会自动显示一个箭头图标,并在子菜单展开时保持高亮。与 Item 一样,它的 icon 属性接受 Phosphor 图标组件或已渲染的元素。

属性类型默认值说明
iconIcon-显示在标签前的图标。
insetboolean-增加左侧内边距,以便与带图标的菜单项对齐。

子菜单项的容器。相对于 SubTrigger 定位。当子菜单位于触发器旁边时,会取消弹层内边距,使第一项与触发器对齐并略微覆盖父菜单。若空间不足而移动到触发器上方或下方,则使用与顶级菜单相同的间距。可通过 sideOffset 或 alignOffset(数字,或根据最终 side 计算的函数)覆盖该行为。

属性类型默认值

该组件没有专有属性,支持标准 HTML 属性。

菜单项之间的视觉分隔线。

属性类型默认值

该组件没有专有属性,支持标准 HTML 属性。

将单选项组织成一组,实现单选行为。同一时间只能选中一项。

属性类型默认值说明
valueany-当前选中单选项的受控值。
defaultValueany-初始选中的值(非受控)。
onValueChange(value: any, event: ChangeEventDetails) => void-选中值变化时的回调。
disabledboolean-为 true 时分组内所有单选项均禁用。

行为类似单选按钮的菜单项。必须放在 RadioGroup 内部使用。

属性类型默认值说明
value*any-该单选项的值。
iconIcon | ReactNode-显示在标签前的图标。
insetboolean-增加左侧内边距,以便与带图标的菜单项对齐。
closeOnClickbooleanfalse点击该菜单项后菜单是否关闭。
disabledboolean-为 true 时该菜单项无法交互。

展示 RadioItem 的选中状态。默认显示对勾标记。

属性类型默认值

该组件没有专有属性,支持标准 HTML 属性。