DropdownMenu
- Component overview: Dropdown menu that displays a list of actions below a trigger element. Supports Default/Danger semantic variants, icon slots, and cascading sub-menus.
- Size baseline: Content min-width 160px, padding-y 8px, radius 5px; Item padding 16px × 8px, font 14px/20px.
- Implementation note: Standard usage via declarative
itemsconfig, including cascading menus throughsubItems. Only use the composition API for advanced scenarios such as CheckboxItem / RadioItem.ui/dropdown-menuretains structural skeleton and animations; the design layer takes over visuals viaunstyledVisual. - Figma spec
Basic Usage
Configure menu items declaratively via items; pass the trigger element as children.
Result
Loading...
Live Editor
render( <DropdownMenu items={[{ label: 'Profile' }, { label: 'Settings' }, { type: 'separator' }, { label: 'Log out' }]} > <Button variant="secondary">Open Menu</Button> </DropdownMenu>, )
Variants
Use variant to distinguish action semantics.
Result
Loading...
Live Editor
render( <DropdownMenu items={[ { label: 'Edit' }, { label: 'Duplicate' }, { type: 'separator' }, { label: 'Delete', variant: 'danger' }, ]} > <Button variant="secondary">Actions</Button> </DropdownMenu>, )
States
Result
Loading...
Live Editor
render( <DropdownMenu items={[ { label: 'Normal item' }, { label: 'Disabled item', disabled: true }, { type: 'separator' }, { label: 'Danger item', variant: 'danger' }, { label: 'Disabled danger', variant: 'danger', disabled: true }, ]} > <Button variant="secondary">States</Button> </DropdownMenu>, )
With Icons
Add a leading icon to menu items via the icon field.
Result
Loading...
Live Editor
const EditIcon = () => ( <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M10.5 1.75L12.25 3.5L10.5 5.25M1.75 12.25H3.5L10.5 5.25L8.75 3.5L1.75 10.5V12.25Z" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round"/> </svg> ) const TrashIcon = () => ( <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M1.75 3.5H12.25M5.25 6.125V10.375M8.75 6.125V10.375M2.625 3.5L3.5 11.375C3.5 11.8582 3.89175 12.25 4.375 12.25H9.625C10.1082 12.25 10.5 11.8582 10.5 11.375L11.375 3.5M4.375 3.5V2.625C4.375 2.14175 4.76675 1.75 5.25 1.75H8.75C9.23325 1.75 9.625 2.14175 9.625 2.625V3.5" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round"/> </svg> ) const ArrowIcon = ({ direction }) => { const rotationMap = { up: 'rotate(-180deg)', right: 'rotate(-90deg)', down: 'rotate(0deg)', left: 'rotate(90deg)', } return ( <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ transform: rotationMap[direction] }} > <path d="M3.5 5.25L7 8.75L10.5 5.25" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" /> </svg> ) } render( <DropdownMenu items={[ { label: 'Edit', icon: <EditIcon /> }, { label: 'Arrow Up', icon: <ArrowIcon direction="up" /> }, { label: 'Arrow Right', icon: <ArrowIcon direction="right" /> }, { label: 'Arrow Down', icon: <ArrowIcon direction="down" /> }, { label: 'Arrow Left', icon: <ArrowIcon direction="left" /> }, { type: 'separator' }, { label: 'Delete', variant: 'danger', icon: <TrashIcon /> }, ]} > <Button variant="secondary">With Icons</Button> </DropdownMenu>, )
Label Groups
Result
Loading...
Live Editor
render( <DropdownMenu items={[ { type: 'label', label: 'My Account' }, { label: 'Profile' }, { label: 'Settings' }, { type: 'separator' }, { type: 'label', label: 'Team' }, { label: 'Team Settings' }, { label: 'Invite Members' }, ]} > <Button variant="secondary">Grouped Menu</Button> </DropdownMenu>, )
Cascading Submenu
Result
Loading...
Live Editor
render( <DropdownMenu items={[ { label: 'New File' }, { label: 'More Options', subItems: [{ label: 'Import' }, { label: 'Export' }], }, { type: 'separator' }, { label: 'Delete', variant: 'danger' }, ]} > <Button variant="secondary">Cascading Menu</Button> </DropdownMenu>, )
Size Spec
Content Container
| Property | Token | Value |
|---|---|---|
| Min width | — | 160px |
| Vertical padding | Spacing_8 | 8px |
| Border radius | Radius_5 | 5px |
| Shadow | Effects/Shadow/Default | 0 0 32px rgba(0,0,0,0.1) |
Item
| Property | Token | Value |
|---|---|---|
| Horizontal padding | Spacing_16 | 16px |
| Vertical padding | Spacing_8 | 8px |
| Font size | Font-Size/Body | 14px |
| Line height | Line-Height/Body | 22px |
| Icon container | — | 20×20px |
Color Tokens
| State | Text Color | Hover Background |
|---|---|---|
| Default | Labels/Secondary#3d3d3d | Grays/Gray-1#ebebeb |
| Danger | Labels/Error#cc3325 | Grays/Gray-1#ebebeb |
| Disabled | Labels/Disabled#a3a3a3 | — |
Props
DropdownMenu (Convenience API)
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | - | Trigger element |
| items | DropdownMenuItemEntry[] | - | Declarative menu item config |
| asChild | boolean | true | Whether to render children as the Trigger element |
| open | boolean | - | Controlled open state |
| onOpenChange | (open: boolean) => void | - | Open state change callback |
| contentClassName | string | - | Custom className for Content |
| side | 'top' | 'right' | 'bottom' | 'left' | 'bottom' | Popup direction |
| align | 'start' | 'center' | 'end' | 'start' | Alignment |
DropdownMenuItemEntry
| Kind | type value | Required fields | Description |
|---|---|---|---|
| Menu item | 'item' (can be omitted) | label | Supports variant, icon, disabled, onSelect, subItems, itemProps |
| Separator | 'separator' | - | Renders as DropdownMenuSeparator |
| Group label | 'label' | label | Renders as DropdownMenuLabel |
DropdownMenuItem
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | - | Item content |
| variant | 'default' | 'danger' | 'default' | Semantic variant |
| icon | ReactNode | - | Leading icon |
| disabled | boolean | false | Disabled state |
| onSelect | (event: Event) => void | - | Select callback |
| className | string | - | Custom className |
Advanced Usage
Prefer items / subItems by default. Only when you need CheckboxItem, RadioItem, or fully custom content composition should you switch to DropdownMenu + DropdownMenuTrigger + DropdownMenuContent.