Dropdown component
Dropdown is a compound menu component: Dropdown owns the open state and positioning, DropdownTrigger wraps whatever element opens it, and DropdownMenu holds DropdownItems (or DropdownSections of them). It supports single/multiple selection, nested submenus, and opt-in virtualization for long lists, on top of full keyboard navigation and typeahead.
Playground
Change a prop on the right and the preview updates. Switch to Code to copy what you see.
Recipes
Copy-paste patterns for the ways Dropdown actually gets used — each one a real, self-contained example.
Action menu
The compound shape: DropdownTrigger wraps the opener, DropdownMenu holds the items. onAction fires with the activated item's key — usually all you need.
<Dropdown>
<DropdownTrigger>
<Button variant="bordered">Actions</Button>
</DropdownTrigger>
<DropdownMenu aria-label="Actions" onAction={(key) => console.log(key)}>
<DropdownItem key="edit">Edit</DropdownItem>
<DropdownItem key="duplicate">Duplicate</DropdownItem>
<DropdownItem key="archive">Archive</DropdownItem>
</DropdownMenu>
</Dropdown>Icons and shortcuts
startContent puts an icon before the label; shortcut renders a keyboard hint aligned to the right.
<DropdownMenu aria-label="Edit menu" onAction={(key) => console.log(key)}>
<DropdownItem key="copy" startContent={<CopyIcon />} shortcut="⌘C">
Copy
</DropdownItem>
<DropdownItem key="paste" startContent={<ClipboardIcon />} shortcut="⌘V">
Paste
</DropdownItem>
</DropdownMenu>Sections with a divider
Group related items under a DropdownSection with a title; showDivider separates a section (like a destructive one) from the rest.
<DropdownMenu aria-label="Profile menu" onAction={(key) => console.log(key)}>
<DropdownSection title="Account" showDivider>
<DropdownItem key="profile">Profile</DropdownItem>
<DropdownItem key="settings">Settings</DropdownItem>
</DropdownSection>
<DropdownSection title="Danger zone">
<DropdownItem key="signout">
<span className="text-danger">Sign out</span>
</DropdownItem>
</DropdownSection>
</DropdownMenu>Single-select menu
selectionMode="single" makes the menu track a choice — pair with selectedKeys/onSelectionChange to control it. A checkmark marks the active item.
const [sort, setSort] = useState("name");
<DropdownMenu
aria-label="Sort by"
selectionMode="single"
selectedKeys={[sort]}
onSelectionChange={(keys) => setSort([...keys][0])}
>
<DropdownItem key="name">Name</DropdownItem>
<DropdownItem key="date">Date created</DropdownItem>
<DropdownItem key="size">Size</DropdownItem>
</DropdownMenu>Disabled item
disabledKeys (or isDisabled on an item) removes an option from keyboard navigation, not just dims it.
<DropdownMenu aria-label="Actions" disabledKeys={["publish"]}>
<DropdownItem key="save">Save draft</DropdownItem>
<DropdownItem key="publish">Publish (needs review)</DropdownItem>
<DropdownItem key="delete">
<span className="text-danger">Delete</span>
</DropdownItem>
</DropdownMenu>The compound shape
DropdownTrigger takes exactly one child — the element that opens the menu on press — and DropdownMenu holds the items. onAction fires with the activated item's key; that's usually all you need instead of an onClick per item.
<Dropdown>
<DropdownTrigger>
<Button variant="bordered">Actions</Button>
</DropdownTrigger>
<DropdownMenu aria-label="Actions" onAction={(key) => handleAction(String(key))}>
<DropdownItem key="edit">Edit</DropdownItem>
<DropdownItem key="duplicate">Duplicate</DropdownItem>
<DropdownItem key="delete" className="text-danger">Delete</DropdownItem>
</DropdownMenu>
</Dropdown>Selection modes
selectionMode defaults to "none" (a plain action menu). Set it to "single" or "multiple" for a menu that tracks its own selected item(s) — pair with selectedKeys/onSelectionChange to control it, or defaultSelectedKeys to just seed the initial state and let Dropdown manage it from there.
<DropdownMenu
aria-label="Sort by"
selectionMode="single"
selectedKeys={[sortKey]}
onSelectionChange={(keys) => setSortKey([...keys][0] as string)}
>
<DropdownItem key="name">Name</DropdownItem>
<DropdownItem key="date">Date</DropdownItem>
</DropdownMenu>Nested submenus
An item with its own DropdownMenu inside it becomes a submenu, opened on hover or ArrowRight — submenuPlacement controls which side it opens on when the default (right-start) would run out of viewport space.
<DropdownItem key="export">
Export
<DropdownMenu aria-label="Export format">
<DropdownItem key="csv">CSV</DropdownItem>
<DropdownItem key="json">JSON</DropdownItem>
</DropdownMenu>
</DropdownItem>Long lists: opt-in virtualization
For a menu with hundreds of items (a searchable list of every country, every user in a workspace), pass virtualization={{ itemHeight }} on DropdownMenu so only the visible slice of items actually renders to the DOM. Leave it off for ordinary menus — it's specifically for lists long enough that full rendering would be the bottleneck.
<DropdownMenu aria-label="Country" items={countries} virtualization={{ itemHeight: 36 }}>
{(country) => <DropdownItem key={country.code}>{country.name}</DropdownItem>}
</DropdownMenu>Positioning and dismissal
placement (12 directional options) plus shouldFlip (on by default, so the menu flips to the opposite side automatically when it would otherwise run off-screen) control layout; isDismissable/isKeyboardDismissDisabled/shouldCloseOnInteractOutside control how it can be closed, and closeOnSelect (on by default) decides whether picking an item closes the menu — turn it off for a multi-select menu you want to stay open across several picks.
Props
The full public API for Dropdown. Every prop is stable and intentionally typed.
Dropdown — Content
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | A DropdownTrigger and a DropdownMenu. |
children
ReactNode
default: —
A DropdownTrigger and a DropdownMenu.
Dropdown — Layout & positioning
| Prop | Type | Default | Description |
|---|---|---|---|
| placement | "bottom-start" | "bottom-end" | "top-start" | ... (12-way) | "bottom-start" | Menu position relative to the trigger. |
| offset | number | 8 | Distance from the trigger. |
| containerPadding | number | 8 | Minimum distance kept from the viewport edge. |
| shouldFlip | boolean | true | Flips placement to stay in the viewport. |
| portalContainer | Element | DocumentFragment | null | — | Custom portal mount target. |
placement
"bottom-start" | "bottom-end" | "top-start" | ... (12-way)
default: "bottom-start"
Menu position relative to the trigger.
offset
number
default: 8
Distance from the trigger.
containerPadding
number
default: 8
Minimum distance kept from the viewport edge.
shouldFlip
boolean
default: true
Flips placement to stay in the viewport.
portalContainer
Element | DocumentFragment | null
default: —
Custom portal mount target.
Dropdown — Behavior & states
| Prop | Type | Default | Description |
|---|---|---|---|
| isOpen | boolean | — | Controlled open state. |
| defaultOpen | boolean | false | Uncontrolled initial open state. |
| isDismissable | boolean | true | Allows dismissing via outside click/Escape. |
| isKeyboardDismissDisabled | boolean | false | Disables closing via Escape. |
| shouldCloseOnInteractOutside | (target: Element) => boolean | — | Custom predicate for outside-interaction dismissal. |
| closeOnSelect | boolean | true | Closes the menu after an item is selected. |
isOpen
boolean
default: —
Controlled open state.
defaultOpen
boolean
default: false
Uncontrolled initial open state.
isDismissable
boolean
default: true
Allows dismissing via outside click/Escape.
isKeyboardDismissDisabled
boolean
default: false
Disables closing via Escape.
shouldCloseOnInteractOutside
(target: Element) => boolean
default: —
Custom predicate for outside-interaction dismissal.
closeOnSelect
boolean
default: true
Closes the menu after an item is selected.
Dropdown — Events
| Prop | Type | Default | Description |
|---|---|---|---|
| onOpenChange | (isOpen: boolean) => void | — | Called when open state changes. |
onOpenChange
(isOpen: boolean) => void
default: —
Called when open state changes.
Dropdown — Styling
| Prop | Type | Default | Description |
|---|---|---|---|
| classNames | Partial<Record<"base" | "content", string>> | — | Per-slot class overrides. |
| className | string | — | Class applied to the root. |
| style | CSSProperties | — | Inline styles for the root. |
classNames
Partial<Record<"base" | "content", string>>
default: —
Per-slot class overrides.
className
string
default: —
Class applied to the root.
style
CSSProperties
default: —
Inline styles for the root.
DropdownTrigger — Content
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactElement | — | The single element that opens the menu on press (required). |
children
ReactElement
default: —
The single element that opens the menu on press (required).
DropdownMenu — Content
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | ((item: T) => ReactElement) | — | Declarative DropdownItem/DropdownSection children, or a render function with items. |
| items | Iterable<T> | — | Dynamic items, used with a render-function children. |
| topContent / bottomContent | ReactNode | — | Content rendered above/below the item list. |
| emptyContent | ReactNode | "No items." | Shown when there are no items. |
children
ReactNode | ((item: T) => ReactElement)
default: —
Declarative DropdownItem/DropdownSection children, or a render function with items.
items
Iterable<T>
default: —
Dynamic items, used with a render-function children.
topContent / bottomContent
ReactNode
default: —
Content rendered above/below the item list.
emptyContent
ReactNode
default: "No items."
Shown when there are no items.
DropdownMenu — Appearance
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "solid" | "bordered" | "light" | "flat" | "faded" | "shadow" | solid | Visual style variant. |
| color | DropdownColor | default | Semantic color. |
| hideSelectedIcon | boolean | false | Hides the selected-item checkmark. |
variant
"solid" | "bordered" | "light" | "flat" | "faded" | "shadow"
default: solid
Visual style variant.
color
DropdownColor
default: default
Semantic color.
hideSelectedIcon
boolean
default: false
Hides the selected-item checkmark.
DropdownMenu — Behavior & states
| Prop | Type | Default | Description |
|---|---|---|---|
| selectionMode | "none" | "single" | "multiple" | none | Item selection behavior. |
| selectedKeys | "all" | Iterable<Key> | — | Controlled selected keys. |
| defaultSelectedKeys | "all" | Iterable<Key> | — | Uncontrolled initial selected keys. |
| disabledKeys | Iterable<Key> | — | Keys of items to disable. |
| disallowEmptySelection | boolean | false | Prevents deselecting the last selected item. |
| autoFocus | boolean | "first" | "last" | false | Focuses an item automatically when the menu opens. |
| hideEmptyContent | boolean | false | Hides the empty-state content entirely. |
| shouldFocusWrap | boolean | false | Wraps keyboard focus from the last item to the first. |
| closeOnSelect | boolean | parent Dropdown's value | Per-menu override of the root's closeOnSelect. |
| virtualization | { itemHeight: number; overscan?: number } | — | Enables fixed-height virtualized rendering for long lists. |
selectionMode
"none" | "single" | "multiple"
default: none
Item selection behavior.
selectedKeys
"all" | Iterable<Key>
default: —
Controlled selected keys.
defaultSelectedKeys
"all" | Iterable<Key>
default: —
Uncontrolled initial selected keys.
disabledKeys
Iterable<Key>
default: —
Keys of items to disable.
disallowEmptySelection
boolean
default: false
Prevents deselecting the last selected item.
autoFocus
boolean | "first" | "last"
default: false
Focuses an item automatically when the menu opens.
hideEmptyContent
boolean
default: false
Hides the empty-state content entirely.
shouldFocusWrap
boolean
default: false
Wraps keyboard focus from the last item to the first.
closeOnSelect
boolean
default: parent Dropdown's value
Per-menu override of the root's closeOnSelect.
virtualization
{ itemHeight: number; overscan?: number }
default: —
Enables fixed-height virtualized rendering for long lists.
DropdownMenu — Motion
| Prop | Type | Default | Description |
|---|---|---|---|
| disableAnimation | boolean | false | Disables the menu's open animation. |
disableAnimation
boolean
default: false
Disables the menu's open animation.
DropdownMenu — Events
| Prop | Type | Default | Description |
|---|---|---|---|
| onAction | (key: Key) => void | — | Called when an item is activated. |
| onSelectionChange | (keys) => void | — | Called when selection changes. |
| onClose | () => void | — | Called when the menu requests to close. |
onAction
(key: Key) => void
default: —
Called when an item is activated.
onSelectionChange
(keys) => void
default: —
Called when selection changes.
onClose
() => void
default: —
Called when the menu requests to close.
DropdownMenu — Styling
| Prop | Type | Default | Description |
|---|---|---|---|
| classNames | DropdownMenuClassNames | — | Per-slot class overrides (base/list/emptyContent). |
| itemClasses | Partial<Record<DropdownItemSlot, string>> | — | Per-slot class overrides applied to every item. |
classNames
DropdownMenuClassNames
default: —
Per-slot class overrides (base/list/emptyContent).
itemClasses
Partial<Record<DropdownItemSlot, string>>
default: —
Per-slot class overrides applied to every item.
DropdownItem — Content
| Prop | Type | Default | Description |
|---|---|---|---|
| children / title | ReactNode | — | Item label. |
| textValue | string | — | Plain-text override for typeahead/accessible naming. |
| description | ReactNode | — | Secondary text below the label. |
| shortcut | ReactNode | — | Keyboard shortcut hint. |
| startContent / endContent | ReactNode | — | Content rendered before/after the label. |
| selectedIcon | ReactNode | — | Overrides the default selected checkmark. |
children / title
ReactNode
default: —
Item label.
textValue
string
default: —
Plain-text override for typeahead/accessible naming.
description
ReactNode
default: —
Secondary text below the label.
shortcut
ReactNode
default: —
Keyboard shortcut hint.
startContent / endContent
ReactNode
default: —
Content rendered before/after the label.
selectedIcon
ReactNode
default: —
Overrides the default selected checkmark.
DropdownItem — Layout & positioning
| Prop | Type | Default | Description |
|---|---|---|---|
| submenuPlacement | DropdownPlacement | "right-start" | Position of a nested DropdownMenu, when this item has one. |
submenuPlacement
DropdownPlacement
default: "right-start"
Position of a nested DropdownMenu, when this item has one.
DropdownItem — Behavior & states
| Prop | Type | Default | Description |
|---|---|---|---|
| showDivider | boolean | false | Shows a divider below this item. |
| href / target / rel / download / ping / referrerPolicy | string | — | Renders the item as a link when href is set. |
| isDisabled | boolean | false | Disables this item. |
| isReadOnly | boolean | false | Not selectable, but not visually disabled. |
| isSelected | boolean | — | Marks this item as selected. Use it when selection is managed outside the menu; otherwise the menu's selectedKeys decide. |
| closeOnSelect | boolean | — | Per-item override of the menu's closeOnSelect. |
| itemKey | Key | — | Explicit key, when not using items+render-function. |
showDivider
boolean
default: false
Shows a divider below this item.
href / target / rel / download / ping / referrerPolicy
string
default: —
Renders the item as a link when href is set.
isDisabled
boolean
default: false
Disables this item.
isReadOnly
boolean
default: false
Not selectable, but not visually disabled.
isSelected
boolean
default: —
Marks this item as selected. Use it when selection is managed outside the menu; otherwise the menu's selectedKeys decide.
closeOnSelect
boolean
default: —
Per-item override of the menu's closeOnSelect.
itemKey
Key
default: —
Explicit key, when not using items+render-function.
DropdownItem — Events
| Prop | Type | Default | Description |
|---|---|---|---|
| onAction / onClose / onClick / onPress / onPressStart / onPressEnd / onPressChange / onPressUp / onKeyDown / onKeyUp | callback | — | Interaction callbacks. |
onAction / onClose / onClick / onPress / onPressStart / onPressEnd / onPressChange / onPressUp / onKeyDown / onKeyUp
callback
default: —
Interaction callbacks.
Accessibility
- Implements the full menu/menuitem ARIA pattern with roving tabindex, arrow-key navigation, and typeahead (typing jumps to the next matching item).
- When an item's visible label is a non-text node (an icon plus a badge, say), pass textValue with the plain-text equivalent so typeahead and the accessible name both still work correctly.
- Always give DropdownMenu its own aria-label (or aria-labelledby) — the trigger button's own label doesn't automatically describe what the opened menu contains.
- isReadOnly items are visible and reachable by keyboard but not selectable — different from isDisabled, which removes them from keyboard navigation entirely.
Best practices
- Keep selectionMode="none" for plain action menus (Edit/Duplicate/Delete) — only reach for "single"/"multiple" when the menu is genuinely representing a choice, not a set of one-shot actions.
- Turn closeOnSelect off for multi-select menus where picking several items in one sitting is the expected flow; leave it on for single actions, where staying open after a pick just adds an extra dismiss step.
- Reserve virtualization for lists long enough to actually need it (order of hundreds of items) — for a typical 5-15 item menu it's unnecessary overhead.
- Use showDivider on an item to separate a destructive action (Delete) from the rest of the menu, rather than relying on color alone to signal it's different.