When to reach for Dropdown, how to fill its menu, and the traps to avoid.
When to use
A Dropdown is a list of actions that opens from something already on screen: a button, a row's overflow icon, an avatar or a right-click. Use it for commands that are real but not frequent enough to earn their own button.
An overflow menu on a row or a card: rename, duplicate, export, delete.
The menu half of a split button, holding variations of the main action.
An account or workspace menu in a header.
A context menu on a canvas or a table, through trigger={["contextMenu"]}.
What to reach for instead
Select when the reader picks a value that a form submits. menu.selectable looks similar, but it has no form value and does not announce the selection.
Menu for navigation that should stay visible, like a sidebar or a header bar.
Popover when the content is not a list of commands: a form, a preview or a paragraph of help.
Tooltip when nothing is clickable and the point is a label.
If you are writing popupRender to put a form in the popup, use Popover. popupRender is for adding a header or footer around the menu.
The items API
Pass menu. It takes the Menu props (items, onClick, selectable) and builds the list for you. The v5 overlay prop is gone.
An item's type is "group", "divider", or left out for a plain item.
Leave out a row with null. A null entry in items is skipped, so a conditional row needs no .filter(Boolean).
Pass item icons as <Icon>. Items have no icon by default. The kit's Icon follows your icon pack, and the submenu arrow already does.
Shortcut hints go in extra. It shows a muted hint at the end of the row. Wiring the key itself is up to you.
The trigger must accept a ref and spread its props. Dropdown attaches its handlers to its one child, so a custom component that drops ...rest never opens the menu. Pass a real button, not a bare string.
Dividers, groups, danger and disabled
Every entry lives in the same array, so the order in your code is the order on screen.
Use { type: "divider" } between groups of meaning, not between every row. Two or three groups read well.
Use { type: "group", label, children } when a group needs a name. The title is a heading and cannot be clicked.
Put the destructive row last, behind a divider, and mark it danger. If the action cannot be undone, have it open a confirmation dialog.
Disable one item rather than the whole Dropdown. A disabled Dropdown cannot open, so the reader never sees what is in it.
Say why a row is disabled in its label, or leave the row out.
Trigger
trigger is an array, ["hover"] by default. The other values are "click" and "contextMenu", and they combine.
Prefer a click trigger. Hover does not work on touch screens. Use ["click"] for overflow buttons and table rows.
Hover suits a header menu the reader passes over. If it flickers on the way to a submenu, raise mouseLeaveDelay (0.1s by default).
contextMenu replaces the browser's menu on the trigger and opens at the pointer. A right-click is hard to discover and has no keyboard equivalent, so offer the same commands from a visible button too.
disabled also disables the trigger. A child that sets its own disabled={false} keeps that value, so it looks live and opens nothing. Leave the prop off the child.
Placement and shifting
Placement is a preference. The menu flips near a viewport edge. Write top and bottom, since topCenter and bottomCenter are deprecated.
The default is bottomLeft, and bottomRight under direction="rtl", so a mirrored layout needs no prop.
Use bottomRight for a trigger at the right edge of a row or header, so the menu opens inward.
The menu is at least as wide as its trigger. A min-w-* on classNames.root loses to that width, so set w-* instead.
arrow is off by default, and { pointAtCenter: true } aims it at the middle of a wide trigger.
getPopupContainer decides whether the menu scrolls and clips with its trigger. Theme colors reach the menu without it.
Nesting
Give an item a children array to open a submenu from it, with an arrow drawn from your icon pack.
Keep to one level. Readers lose deeper menus on the way from a row to its submenu.
Keep common commands flat and nest only their variations, the way “Export” nests its formats.
Submenus open in their own popup, so style them through classNames, which reaches nested items too.
A Dropdown's menu is always vertical, so an accordion inside a dropdown is not an option.
When the menu keeps a selection
Use Select for form values.menu.selectable suits a sort order or a view mode, and adding menu.multiple keeps the menu open between clicks. A value a form submits belongs in Select.
Show the current value on the trigger. The highlight only shows while the menu is open.
To keep the menu open after other clicks, control open and ignore closes whose info.source is "menu".
popupRender wraps or replaces the menu. Return the node it gives you inside your own markup to keep the menu's hover and keyboard navigation, or ignore it to show anything. When you wrap it, add shadow-none to menu.className so there is one shadow.
The split button
Build split buttons with Space.Compact.Dropdown.Button is deprecated and logs a warning. Put a Button and a Dropdown in Space.Compact, and give the icon-only half an aria-label.
Each half is an ordinary Button, so it takes its own color, danger, loading and icon.
The leading half is the main action and the menu holds its variations. Two unrelated actions are two buttons.
Set placement="bottomRight" on the menu half, so the menu hangs under the icon.
Styling the menu
Style the menu with classNames. The menu renders in a portal, so className and selectors on the trigger never reach it. Use classNames.root for the popup and itemIcon, itemTitle or itemContent for the items.