Dropdown
Migration guide for Dropdown from HeroUI v2 to v3
Refer to the v3 Dropdown documentation for complete API reference, styling guide, and advanced examples. This guide only focuses on migrating from HeroUI v2.
Structure Changes
In v2, Dropdown used separate components: DropdownTrigger, DropdownMenu, DropdownItem, DropdownSection:
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>
);
}In v3, Dropdown uses a compound component pattern with explicit subcomponents:
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>
);
}Key Changes
1. Component Structure
v2: Separate components: DropdownTrigger, DropdownMenu, DropdownItem, DropdownSection
v3: Compound components: Dropdown.Trigger, Dropdown.Popover, Dropdown.Menu, Dropdown.Item, Dropdown.Section
2. Component Name Changes
| v2 Component | v3 Component | Notes |
|---|---|---|
DropdownTrigger | Dropdown.Trigger | Same functionality |
DropdownMenu | Dropdown.Menu | Wrapped in Dropdown.Popover |
DropdownItem | Dropdown.Item | Use id and textValue; keep key on list items |
DropdownSection | Dropdown.Section | Same functionality |
| - | Dropdown.Popover | New wrapper component (required) |
3. Item Identification
v2: Item content passed as children; React's key was used for both list reconciliation and item identity (selection, focus).
v3: Item content must use Label component for text. Give each item an id (for state/focus) and textValue (for accessibility when content isn't plain text). Keep React's key on items in lists.
4. Prop Changes
| v2 Prop | v3 Location | Notes |
|---|---|---|
variant, color (on DropdownMenu) | - | Removed (no menu variants) |
classNames, itemClasses (on DropdownMenu) | - | Use className on Menu and items |
color (on DropdownItem) | Dropdown.Item | Use variant="danger" for danger |
title | - | Use Label component as child |
description | - | Use Description component as child |
shortcut | - | Use Kbd component as child |
startContent, endContent | - | Use icon/components as children |
selectedIcon | - | Use Dropdown.ItemIndicator |
showDivider | - | Use Separator between items |
classNames (on DropdownItem) | - | Use className on item |
isSelected | Dropdown.Menu | Use selectedKeys on Menu |
isDisabled | Dropdown.Menu | Use disabledKeys on Menu |
trigger (on Dropdown) | Dropdown | Still supported: "press" (default) or "longPress" |
5. New Components
Dropdown.Popover- Required wrapper aroundDropdown.MenuDropdown.ItemIndicator- For selection indicators (checkmark/dot)Dropdown.SubmenuTrigger- For submenu functionalityDropdown.SubmenuIndicator- For submenu chevron indicator
Migration Examples
With Action Handler
<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>Item Content
{/* 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>Danger 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>With Sections
<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>Selection
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>With Keyboard Shortcuts
<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>The slot="keyboard" prop on Kbd positions the shortcut at the end of the menu item, replacing the v2 shortcut prop.
With Submenus
{/* 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>Use Dropdown.SubmenuTrigger to wrap an item that opens a nested menu. Place Dropdown.SubmenuIndicator inside the item to render a chevron icon.
Long Press Trigger
<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>The trigger prop on Dropdown accepts "press" (default) or "longPress" to control how the menu is opened.
Component Anatomy
The v3 Dropdown follows this structure:
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.ItemSummary
- Component Structure: Must use compound components (
Dropdown.Trigger,Dropdown.Popover,Dropdown.Menu, etc.) - Dropdown.Popover Required:
Dropdown.Menumust be wrapped inDropdown.Popover - Label Component: Item text must use
Labelcomponent - Description Component: Use
Descriptioncomponent instead ofdescriptionprop - Kbd Component: Use
Kbdwithslot="keyboard"instead of theshortcutprop — the slot positions the shortcut at the end of the menu item - Icons as Children: Icons go as first child, not
startContentprop - Separator Component: Use
Separatorinstead ofshowDividerprop - ItemIndicator Component: Use
Dropdown.ItemIndicatorfor selection indicators - Variant Instead of Color: Use
variant="danger"instead ofcolor="danger" - No Menu Variants:
Dropdown.Menuno longer supportsvariantorcolorprops - ClassNames Removed: Use
classNameprops on individual components - Submenu Support: Use
Dropdown.SubmenuTriggerto wrap an item that opens a nested menu, andDropdown.SubmenuIndicatorinside the item for a chevron icon - Trigger Prop: Use
trigger="longPress"onDropdownroot to open the menu on long press instead of the default press