Dropdown Menu
A contextual menu that opens on trigger. Built on Radix DropdownMenu for keyboard navigation, focus management, and a11y; animated with interruptible enter/exit transitions.
Installation#
Usage#
import {
DropdownMenu, DropdownMenuTrigger, DropdownMenuContent,
DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator,
DropdownMenuShortcut,
} from "@/components/ui/dropdown-menu"
import { Button } from "@/components/ui/button"
import { GearSix as Gear, SignOut, UserCircle } from "koala-icons"
export function Example() {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">My account</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-48">
<DropdownMenuLabel>My account</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>
<UserCircle /> Profile
<DropdownMenuShortcut>⌘P</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<Gear /> Settings
<DropdownMenuShortcut>⌘,</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
<SignOut /> Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}Anatomy#
import {
DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuLabel,
DropdownMenuGroup, DropdownMenuItem, DropdownMenuItemText, DropdownMenuItemDescription,
DropdownMenuShortcut, DropdownMenuCheckboxItem, DropdownMenuRadioGroup,
DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub,
DropdownMenuSubTrigger, DropdownMenuSubContent,
} from "@/components/ui/dropdown-menu"
<DropdownMenu>
<DropdownMenuTrigger />
<DropdownMenuContent>
<DropdownMenuLabel />
<DropdownMenuGroup>
<DropdownMenuItem>
<DropdownMenuItemText>
<DropdownMenuItemDescription />
</DropdownMenuItemText>
<DropdownMenuShortcut />
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuCheckboxItem />
<DropdownMenuRadioGroup>
<DropdownMenuRadioItem />
</DropdownMenuRadioGroup>
<DropdownMenuSeparator />
<DropdownMenuSub>
<DropdownMenuSubTrigger />
<DropdownMenuSubContent />
</DropdownMenuSub>
</DropdownMenuContent>
</DropdownMenu>Avatar trigger#
Any element can be the trigger via asChild. A common pattern is a header avatar that opens an account menu, with the signed-in identity repeated as a non-interactive label at the top.
Icon button trigger#
A ghost iconOnly button makes a compact “more actions” menu for table rows and card headers. Align the content to the trigger edge with align="end".
Keyboard shortcuts#
DropdownMenuShortcut renders the small gray Kbd chip at the row's end. It is purely decorative: wire up the actual key binding in your app with a global listener.
Checkbox items#
Switch items#
For a setting that applies the moment it flips, pass variant="switch" to a checkbox item. The row draws a switch on the same line as the shortcut and the check, and the menu stays open when it is toggled (closeOnSelect is off for switches), so several can be set in one visit. The row is still a menu checkbox with its checked state announced; the switch is its drawing, not a second control.
Links#
A row that navigates stays a menu item: wrap the link with asChild, so it keeps the row’s styling, arrow-key focus and Enter to activate, while Next.js client navigation and Cmd/Ctrl-click keep working. Mark a link that leaves the site with a trailing ArrowUpRight at size-4.
Descriptions#
A navigation menu often needs more than a label: a title over a sentence that says where the row goes. Put both in a DropdownMenuItemText after the leading icon, the sentence in a DropdownMenuItemDescription. A described row pins its icon to the title’s line instead of centering it on the block, and the sentence steps back by color and weight at the same size, so the title keeps the hierarchy. Give the content a wider measure (w-80) so the sentences wrap readably. Both parts are optional: a row without them stays a plain item.
Radio items#
Navigation#
By default a sub-menu is nested: it flies out beside its row. Pass submenu="navigation" and it drills in place instead: its rows replace the menu's behind a Back row, and the surface resizes to fit. Use it for a deep menu, or one with no room beside it. The parts and the rows are the same, and so are the keys: → goes in, and ← or Esc steps back one layer before the menu closes. It is the Dock's panel, opened from a trigger.
API reference#
DropdownMenu
The root, Radix DropdownMenu.Root; renders no DOM. Every part that renders an element accepts className, merged last.
| Prop | Type | Default | Description |
|---|---|---|---|
| open | boolean | - | Controlled open state. |
| defaultOpen | boolean | false | Initial open state when uncontrolled. |
| onOpenChange | (open: boolean) => void | - | Called when the menu opens or closes. |
| modal | boolean | true | Block pointer interaction with the page and trap focus while open. |
| dir | "ltr" | "rtl" | - | Reading direction; flips sub-menus and arrow keys. |
DropdownMenuTrigger
Opens the menu, Radix DropdownMenu.Trigger. Renders a <button> and forwards all its props.
| Prop | Type | Default | Description |
|---|---|---|---|
| asChild | boolean | false | Merge onto your own element (a Button, an avatar button) instead of rendering a <button>. |
| Data attribute | Description |
|---|---|
| data-state | "open" or "closed". |
| data-disabled | Present when the trigger is disabled. |
DropdownMenuContent
The menu surface, Radix DropdownMenu.Content portalled to the body (or to the full-screen element); forwards all its props.
| Prop | Type | Default | Description |
|---|---|---|---|
| submenu | "nested" | "navigation" | "nested" | How every DropdownMenuSub opens: a flyout beside its row, or a layer that replaces the rows in place behind a Back row. |
| side | "top" | "right" | "bottom" | "left" | "bottom" | Preferred side of the trigger; the menu flips when there is no room. |
| align | "start" | "center" | "end" | "center" | Alignment against the trigger. |
| sideOffset | number | 6 | Gap in pixels between the trigger and the menu. |
| alignOffset | number | 0 | Shift in pixels along the alignment axis. |
| collisionPadding | number | 0 | Distance in pixels kept from the viewport edge. |
| loop | boolean | false | Arrow keys wrap from the last row to the first. |
| container | HTMLElement | null | - | Portal target. Defaults to the body, or the element that is full screen when the menu opens. |
| density | "comfortable" | "compact" | "compact" | Row height for every row inside, 36px or 32px. Unset, the rows follow the nearest DensityProvider. |
| onCloseAutoFocus | (event: Event) => void | - | Called as focus returns to the trigger on close; preventDefault() to send it elsewhere. |
| onEscapeKeyDown | (event: KeyboardEvent) => void | - | Called on Escape; in navigation mode Escape first steps back a layer. |
| onInteractOutside | (event: Event) => void | - | Called on a pointer down or focus outside the menu; preventDefault() keeps it open. |
| Data attribute | Description |
|---|---|
| data-state | "open" or "closed". |
| data-side | The side the menu actually opened on, after collisions. |
| data-align | "start", "center" or "end". |
| data-submenu | "nested" or "navigation". |
| CSS variable | Description |
|---|---|
| --surface | Set to var(--popover), read by nested Inputs and Selects. |
| --radix-dropdown-menu-trigger-width | The trigger's width, for a menu that matches it (w-(--radix-dropdown-menu-trigger-width)). |
| --radix-dropdown-menu-content-available-height | Height left between the trigger and the viewport edge, for capping a long menu. |
| --radix-dropdown-menu-content-transform-origin | The trigger-side origin, for a custom entrance. |
DropdownMenuItem
A row, Radix DropdownMenu.Item (a <div role="menuitem">); forwards all its props. Put a leading icon first and a DropdownMenuShortcut last.
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "default" | "destructive" | "default" | Destructive tints the label, the leading icon and the highlight red. |
| inset | boolean | false | Indent the text to line up with icon-bearing siblings. |
| onSelect | (event: Event) => void | - | Called when the row is chosen; preventDefault() keeps the menu open. |
| disabled | boolean | false | Dims the row and skips it in keyboard navigation. |
| textValue | string | - | Text used for typeahead when the row's content is not plain text. |
| asChild | boolean | false | Merge onto your own element, e.g. a next/link, keeping the row's styling and keys. |
| density | "comfortable" | "compact" | "compact" | Row height, 36px or 32px. Falls back to the menu's density, then the nearest DensityProvider. |
| Data attribute | Description |
|---|---|
| data-highlighted | Present on the row under the pointer or keyboard focus. |
| data-disabled | Present when the row is disabled. |
| data-inset | Present on an inset row. |
DropdownMenuItemText
The title and description column of a described row, placed after the leading icon. Forwards all native <span> props.
DropdownMenuItemDescription
The muted sentence under a row's title, inside DropdownMenuItemText; the leading icon then pins to the title's line. Forwards all native <span> props.
DropdownMenuCheckboxItem
A toggle row, Radix DropdownMenu.CheckboxItem (role="menuitemcheckbox"); forwards all its props.
| Prop | Type | Default | Description |
|---|---|---|---|
| checked | boolean | "indeterminate" | - | The checked state. |
| onCheckedChange | (checked: boolean) => void | - | Called when the row is toggled. |
| variant | "check" | "switch" | "check" | An ink check on the right, or a mini Switch on the same line for a setting that applies at once. |
| closeOnSelect | boolean | true | Close the menu on toggle. Defaults to false when variant="switch". |
| disabled | boolean | false | Dims the row and skips it in keyboard navigation. |
| density | "comfortable" | "compact" | "compact" | Row height, 36px or 32px. Falls back to the menu's density, then the nearest DensityProvider. |
| Data attribute | Description |
|---|---|
| data-state | "checked", "unchecked" or "indeterminate". |
| data-highlighted | Present on the row under the pointer or keyboard focus. |
| data-disabled | Present when the row is disabled. |
DropdownMenuRadioGroup
Groups radio rows into one exclusive choice, Radix DropdownMenu.RadioGroup. Forwards all native <div> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | - | The selected value. |
| onValueChange | (value: string) => void | - | Called when another row is chosen. |
DropdownMenuRadioItem
One choice in a DropdownMenuRadioGroup, Radix DropdownMenu.RadioItem; shows the same ink check as a checkbox row. Forwards all its props.
| Prop | Type | Default | Description |
|---|---|---|---|
| value* | string | - | The value this row selects. |
| disabled | boolean | false | Dims the row and skips it in keyboard navigation. |
| density | "comfortable" | "compact" | "compact" | Row height, 36px or 32px. Falls back to the menu's density, then the nearest DensityProvider. |
| Data attribute | Description |
|---|---|
| data-state | "checked" or "unchecked". |
| data-highlighted | Present on the row under the pointer or keyboard focus. |
| data-disabled | Present when the row is disabled. |
DropdownMenuLabel
A muted caption over a run of rows, Radix DropdownMenu.Label; not focusable. Forwards all native <div> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| inset | boolean | false | Indent to line up with icon-bearing rows. |
| density | "comfortable" | "compact" | "compact" | Row height, 36px or 32px. Falls back to the menu's density, then the nearest DensityProvider. |
DropdownMenuGroup
Groups related rows (role="group"), Radix DropdownMenu.Group. Forwards all native <div> props.
DropdownMenuSeparator
A hairline between groups that stops at the ring's inner edge, Radix DropdownMenu.Separator. Forwards all native <div> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| density | "comfortable" | "compact" | "compact" | Row height, 36px or 32px. Falls back to the menu's density, then the nearest DensityProvider. |
DropdownMenuShortcut
A display-only key hint pushed to the row's end: the small gray Kbd chip. Forwards all Kbd props; it binds no key.
DropdownMenuSub
Pairs a DropdownMenuSubTrigger with the DropdownMenuSubContent it opens, Radix DropdownMenu.Sub; renders no DOM.
| Prop | Type | Default | Description |
|---|---|---|---|
| open | boolean | - | Controlled open state. Ignored in navigation mode, where the menu owns the open branch. |
| defaultOpen | boolean | false | Initial open state when uncontrolled; nested mode only. |
| onOpenChange | (open: boolean) => void | - | Called when the sub-menu opens or closes; nested mode only. |
DropdownMenuSubTrigger
The row that opens a sub-menu, with a trailing caret; Radix DropdownMenu.SubTrigger, or a plain item in navigation mode. Forwards all its props.
| Prop | Type | Default | Description |
|---|---|---|---|
| inset | boolean | false | Indent the text to line up with icon-bearing siblings. |
| disabled | boolean | false | Dims the row and skips it in keyboard navigation. |
| textValue | string | - | Text used for typeahead. |
| density | "comfortable" | "compact" | "compact" | Row height, 36px or 32px. Falls back to the menu's density, then the nearest DensityProvider. |
| Data attribute | Description |
|---|---|
| data-state | "open" or "closed" (nested mode). |
| data-highlighted | Present on the row under the pointer or keyboard focus. |
| data-disabled | Present when the row is disabled. |
DropdownMenuSubContent
The sub-menu, Radix DropdownMenu.SubContent: a surface beside its row in nested mode, or rows that take the menu's place in navigation mode (where className and the positioning props do not apply).
| Prop | Type | Default | Description |
|---|---|---|---|
| backLabel | string | "Back" | Navigation mode: label of the row that returns to the parent layer. |
| sideOffset | number | 0 | Gap in pixels between the row and the flyout. |
| alignOffset | number | 0 | Shift in pixels along the row. |
| loop | boolean | false | Arrow keys wrap from the last row to the first. |
| density | "comfortable" | "compact" | "compact" | Row height, 36px or 32px. Falls back to the menu's density, then the nearest DensityProvider. |
| Data attribute | Description |
|---|---|
| data-state | "open" or "closed". |
| data-side | The side the flyout opened on. |
DropdownMenuPortal
Radix DropdownMenu.Portal, exported for custom compositions; DropdownMenuContent already portals itself.
| Prop | Type | Default | Description |
|---|---|---|---|
| container | HTMLElement | null | document.body | Where the portal mounts. |
| forceMount | true | - | Keep the children mounted while closed. |