ProComponents, templates & AI tooling
HeroUI
27.7k

Dropdown

Dropdown 从 HeroUI v2 到 v3 的迁移指南。

完整的 API 参考、样式指南与高级示例请参阅 v3 Dropdown 文档。本指南只关注从 HeroUI v2 的迁移。

结构变化

在 v2 中,Dropdown 使用相互独立的组件:DropdownTriggerDropdownMenuDropdownItemDropdownSection

import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem, Button } from "@heroui/react";

export default function App() {
  return (
    <Dropdown>
      <DropdownTrigger>
        <Button>Open Menu</Button>
      </DropdownTrigger>
      <DropdownMenu>
        <DropdownItem key="new">New file</DropdownItem>
        <DropdownItem key="copy">Copy link</DropdownItem>
      </DropdownMenu>
    </Dropdown>
  );
}

在 v3 中,Dropdown 采用复合组件模式,并提供显式的子组件结构:

import { Dropdown, Button, Label } from "@heroui/react";

export default function App() {
  return (
    <Dropdown>
      <Button>Open Menu</Button>
      <Dropdown.Popover>
        <Dropdown.Menu>
          <Dropdown.Item id="new" textValue="New file">
            <Label>New file</Label>
          </Dropdown.Item>
          <Dropdown.Item id="copy" textValue="Copy link">
            <Label>Copy link</Label>
          </Dropdown.Item>
        </Dropdown.Menu>
      </Dropdown.Popover>
    </Dropdown>
  );
}

主要变化

1. 组件结构

v2: 相互独立的组件:DropdownTriggerDropdownMenuDropdownItemDropdownSection
v3: 复合组件:Dropdown.TriggerDropdown.PopoverDropdown.MenuDropdown.ItemDropdown.Section

2. 组件名称变更

v2 组件v3 组件说明
DropdownTriggerDropdown.Trigger功能相同
DropdownMenuDropdown.Menu需包裹在 Dropdown.Popover
DropdownItemDropdown.Item使用 idtextValue;列表项保留 key
DropdownSectionDropdown.Section功能相同
Dropdown.Popover新增包裹组件(必填)

3. 菜单项标识

v2: 菜单项内容通过 children 传递;React 的 key 同时用于列表调和与菜单项标识(选择、焦点)。
v3: 菜单项的可见文本必须使用 Label 组件。为每个菜单项提供 id(状态/焦点)与 textValue(当内容不是纯文本时用于无障碍)。列表中的菜单项仍应保留 React 的 key

4. Prop 变更

v2 propv3 位置说明
variantcolor(在 DropdownMenu 上)已移除(菜单不再有 variant/color)
classNamesitemClasses(在 DropdownMenu 上)Menu 与菜单项上使用 className
color(在 DropdownItem 上)Dropdown.Item使用 variant="danger" 表示危险操作
title使用 Label 子组件
description使用 Description 子组件
shortcut使用 Kbd 子组件
startContentendContent将图标或组件作为 children 放置
selectedIcon使用 Dropdown.ItemIndicator
showDivider在菜单项之间使用 Separator
classNames(在 DropdownItem 上)在菜单项上使用 className
isSelectedDropdown.MenuMenu 上使用 selectedKeys
isDisabledDropdown.MenuMenu 上使用 disabledKeys
trigger(在 Dropdown 上)Dropdown仍支持:"press"(默认)或 "longPress"

5. 新增子组件

  • Dropdown.PopoverDropdown.Menu 的必填外层包裹
  • Dropdown.ItemIndicator:用于选择指示(对勾/圆点等)
  • Dropdown.SubmenuTrigger:子菜单触发结构
  • Dropdown.SubmenuIndicator:子菜单 chevron 指示

迁移示例

使用 onAction

<DropdownMenu onAction={(key) => alert(key)}>
  <DropdownItem key="new">New file</DropdownItem>
</DropdownMenu>
<Dropdown.Menu onAction={(key) => alert(key)}>
  <Dropdown.Item id="new" textValue="New file">
    <Label>New file</Label>
  </Dropdown.Item>
</Dropdown.Menu>

菜单项内容

{/* With icon */}
<DropdownItem
  key="new"
  startContent={<AddNoteIcon />}
>
  New file
</DropdownItem>

{/* With description */}
<DropdownItem
  key="edit"
  description="Edit the file"
>
  Edit file
</DropdownItem>

{/* With shortcut */}
<DropdownItem
  key="copy"
  shortcut="⌘C"
>
  Copy
</DropdownItem>
import { Icon } from "@iconify/react";
import { Label, Description, Kbd } from "@heroui/react";

{/* With icon */}
<Dropdown.Item id="new" textValue="New file">
  <Icon icon="gravity-ui:square-plus" />
  <Label>New file</Label>
</Dropdown.Item>

{/* With description */}
<Dropdown.Item id="edit" textValue="Edit file">
  <Label>Edit file</Label>
  <Description>Edit the file</Description>
</Dropdown.Item>

{/* With shortcut */}
<Dropdown.Item id="copy" textValue="Copy">
  <Label>Copy</Label>
  <Kbd slot="keyboard" variant="light">
    <Kbd.Abbr keyValue="command" />
    <Kbd.Content>C</Kbd.Content>
  </Kbd>
</Dropdown.Item>

危险菜单项

<DropdownItem
  key="delete"
  className="text-danger"
  color="danger"
>
  Delete file
</DropdownItem>
<Dropdown.Item id="delete" textValue="Delete file" variant="danger">
  <Label>Delete file</Label>
</Dropdown.Item>

使用分组(Section)

<DropdownMenu>
  <DropdownSection showDivider title="Actions">
    <DropdownItem key="new">New file</DropdownItem>
    <DropdownItem key="edit">Edit file</DropdownItem>
  </DropdownSection>
  <DropdownSection title="Danger zone">
    <DropdownItem key="delete" color="danger">Delete file</DropdownItem>
  </DropdownSection>
</DropdownMenu>
import { Header, Separator } from "@heroui/react";

<Dropdown.Menu>
  <Dropdown.Section>
    <Header>Actions</Header>
    <Dropdown.Item id="new" textValue="New file">
      <Label>New file</Label>
    </Dropdown.Item>
    <Dropdown.Item id="edit" textValue="Edit file">
      <Label>Edit file</Label>
    </Dropdown.Item>
  </Dropdown.Section>
  <Separator />
  <Dropdown.Section>
    <Header>Danger zone</Header>
    <Dropdown.Item id="delete" textValue="Delete file" variant="danger">
      <Label>Delete file</Label>
    </Dropdown.Item>
  </Dropdown.Section>
</Dropdown.Menu>

选择

import { useState } from "react";

{/* Single selection */}
const [singleSelected, setSingleSelected] = useState(new Set(["text"]));
<DropdownMenu
  selectedKeys={singleSelected}
  selectionMode="single"
  onSelectionChange={setSingleSelected}
>
  <DropdownItem key="text">Text</DropdownItem>
  <DropdownItem key="number">Number</DropdownItem>
</DropdownMenu>

{/* Multiple selection */}
const [multiSelected, setMultiSelected] = useState(new Set(["bold"]));
<DropdownMenu
  selectedKeys={multiSelected}
  selectionMode="multiple"
  onSelectionChange={setMultiSelected}
>
  <DropdownItem key="bold">Bold</DropdownItem>
  <DropdownItem key="italic">Italic</DropdownItem>
</DropdownMenu>
import { useState } from "react";

{/* Single selection */}
const [singleSelected, setSingleSelected] = useState(new Set(["text"]));
<Dropdown.Menu
  selectedKeys={singleSelected}
  selectionMode="single"
  onSelectionChange={setSingleSelected}
>
  <Dropdown.Item id="text" textValue="Text">
    <Dropdown.ItemIndicator />
    <Label>Text</Label>
  </Dropdown.Item>
  <Dropdown.Item id="number" textValue="Number">
    <Dropdown.ItemIndicator />
    <Label>Number</Label>
  </Dropdown.Item>
</Dropdown.Menu>

{/* Multiple selection */}
const [multiSelected, setMultiSelected] = useState(new Set(["bold"]));
<Dropdown.Menu
  selectedKeys={multiSelected}
  selectionMode="multiple"
  onSelectionChange={setMultiSelected}
>
  <Dropdown.Item id="bold" textValue="Bold">
    <Dropdown.ItemIndicator />
    <Label>Bold</Label>
  </Dropdown.Item>
  <Dropdown.Item id="italic" textValue="Italic">
    <Dropdown.ItemIndicator />
    <Label>Italic</Label>
  </Dropdown.Item>
</Dropdown.Menu>

键盘快捷键

<DropdownItem key="copy" shortcut="⌘C">
  Copy
</DropdownItem>
import { Label, Kbd } from "@heroui/react";

<Dropdown.Item id="copy" textValue="Copy">
  <Label>Copy</Label>
  <Kbd slot="keyboard" variant="light">
    <Kbd.Abbr keyValue="command" />
    <Kbd.Content>C</Kbd.Content>
  </Kbd>
</Dropdown.Item>

Kbdslot="keyboard" prop 会把快捷键放在菜单项末尾,用以替代 v2 的 shortcut prop。

子菜单

{/* v2 did not have built-in submenu support */}
import { Label } from "@heroui/react";

<Dropdown.Menu>
  <Dropdown.Item id="copy-link" textValue="Copy Link">
    <Label>Copy Link</Label>
  </Dropdown.Item>
  <Dropdown.SubmenuTrigger>
    <Dropdown.Item id="share" textValue="Share">
      <Label>Share</Label>
      <Dropdown.SubmenuIndicator />
    </Dropdown.Item>
    <Dropdown.Popover>
      <Dropdown.Menu>
        <Dropdown.Item id="whatsapp" textValue="WhatsApp">
          <Label>WhatsApp</Label>
        </Dropdown.Item>
        <Dropdown.Item id="telegram" textValue="Telegram">
          <Label>Telegram</Label>
        </Dropdown.Item>
      </Dropdown.Menu>
    </Dropdown.Popover>
  </Dropdown.SubmenuTrigger>
</Dropdown.Menu>

使用 Dropdown.SubmenuTrigger 包裹会打开嵌套菜单的菜单项;在菜单项内放置 Dropdown.SubmenuIndicator 以显示子菜单指示图标。

长按触发

<Dropdown>
  <DropdownTrigger>
    <Button>Long press me</Button>
  </DropdownTrigger>
  <DropdownMenu>
    <DropdownItem key="cut">Cut</DropdownItem>
    <DropdownItem key="copy">Copy</DropdownItem>
  </DropdownMenu>
</Dropdown>
<Dropdown trigger="longPress">
  <Button>Long press me</Button>
  <Dropdown.Popover>
    <Dropdown.Menu>
      <Dropdown.Item id="cut" textValue="Cut">
        <Label>Cut</Label>
      </Dropdown.Item>
      <Dropdown.Item id="copy" textValue="Copy">
        <Label>Copy</Label>
      </Dropdown.Item>
    </Dropdown.Menu>
  </Dropdown.Popover>
</Dropdown>

根组件 Dropdowntrigger prop 接受 "press"(默认)或 "longPress",用于控制如何打开菜单。

组件结构(Anatomy)

v3 的 Dropdown 结构如下:

Dropdown (Root) — accepts trigger="press" | "longPress"
  ├── Dropdown.Trigger (optional, defaults to first child)
  ├── Dropdown.Popover (required wrapper)
  │   └── Dropdown.Menu
  │       ├── Dropdown.Item
  │       │   ├── Icon (optional, first child)
  │       │   ├── Label (required for text)
  │       │   ├── Description (optional)
  │       │   ├── Kbd slot="keyboard" (optional, for shortcuts)
  │       │   └── Dropdown.ItemIndicator (optional, for selection)
  │       ├── Separator (for dividers)
  │       ├── Dropdown.Section
  │       │   ├── Header (optional)
  │       │   └── Dropdown.Item
  │       └── Dropdown.SubmenuTrigger
  │           ├── Dropdown.Item
  │           │   ├── Label
  │           │   └── Dropdown.SubmenuIndicator (chevron icon)
  │           └── Dropdown.Popover
  │               └── Dropdown.Menu
  │                   └── Dropdown.Item

总结

  1. 组件结构:必须使用复合组件(Dropdown.TriggerDropdown.PopoverDropdown.Menu 等)。
  2. Dropdown.Popover 为必填Dropdown.Menu 必须包裹在 Dropdown.Popover 内。
  3. Label 组件:菜单项文本必须使用 Label
  4. Description 组件:用 Description 替代 description prop。
  5. Kbd 组件:用带 slot="keyboard"Kbd 替代 shortcut prop;slot 用于把快捷键放到菜单项末尾。
  6. 图标作为 children:图标作为第一个 child 放置,不再使用 startContent prop。
  7. Separator 组件:用 Separator 替代 showDivider prop。
  8. ItemIndicator 组件:用 Dropdown.ItemIndicator 表示选择状态。
  9. 用 variant 表示危险:用 variant="danger" 替代 color="danger"
  10. 菜单样式 prop 移除Dropdown.Menu 不再支持 variantcolor prop。
  11. classNames 移除:在各自子组件上使用 className
  12. 子菜单:用 Dropdown.SubmenuTrigger 包裹会打开嵌套菜单的项,并在项内使用 Dropdown.SubmenuIndicator
  13. trigger prop:在根 Dropdown 上使用 trigger="longPress" 以长按打开菜单(默认为按压打开)。

本页目录