Dropdown Menu
Menus with groups, checkable items, submenus and a gliding highlight.
Built on Icons. Installs @floating-ui/react-dom and motion.
Northlight / northlight-web
northlight.dev
Installation
npx shadcn@latest add https://neonkit.heyaadi.com/r/dropdown-menu.jsonUsage
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>Actions</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem icon={<Copy />} shortcut="⌘C">Copy URL</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="danger">Delete project</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>Examples
Account menu
Without asChild the trigger renders a plain button you can style. Labels take any content.
Checkbox and radio items
Call event.preventDefault() in onSelect to flip several options without the menu closing.
Nested submenus
Submenus open on hover after a short delay, or with ArrowRight. Moving diagonally toward an open submenu won't close it.
API reference
DropdownMenu
Holds the open state. Renders no element of its own.
| Prop | Type | Default |
|---|---|---|
open | booleanControlled open state. | None |
defaultOpen | booleanInitial state when uncontrolled. | false |
onOpenChange | (open: boolean) => voidCalled when the trigger, a selection, Escape, Tab or an outside click asks to change state. | None |
color | NeonColorPalette name or hex. Without it the menu takes the trigger's color, then --neon-accent, then cyan. | inherited |
anchorPoint | { x: number; y: number }Opens the menu at this viewport point instead of against the trigger. Context Menu is built on it. | None |
DropdownMenuTrigger
Renders a button that toggles the menu. Also accepts every native button attribute.
| Prop | Type | Default |
|---|---|---|
asChild | booleanMerges trigger behaviour, ARIA and ref onto its single child, e.g. a Button. | false |
DropdownMenuContent
The menu panel, portaled and positioned with floating-ui. It flips and shifts to stay on screen and scrolls when it's taller than the space. Inside a modal dialog it portals into the dialog. --menu-trigger-width holds the trigger's width, so className="w-[var(--menu-trigger-width)]" matches it.
| Prop | Type | Default |
|---|---|---|
side | "top"Preferred side of the trigger. | "bottom" |
align | "start"Alignment along that side. | "start" |
sideOffset | numberGap from the trigger in px. 2 when opened at an anchorPoint. | 6 |
alignOffset | numberNudge along the alignment axis in px. | 0 |
loop | booleanArrow keys wrap from the last item to the first and back. | true |
DropdownMenuItem
A role="menuitem". Also accepts native div attributes.
| Prop | Type | Default |
|---|---|---|
icon | ReactNodeLeading icon. Lights up while the item is highlighted. | None |
shortcut | ReactNodeRight-aligned hint such as ⌘K. Display only: bind the key yourself. | None |
variant | "default"danger turns the text, icon and highlight red. | "default" |
asChild | booleanRenders the single child (e.g. a link) as the item. Enter activates it like a click. | false |
disabled | booleanVisible but skipped by the keyboard and ignored on click. | false |
textValue | stringText for typeahead when the label isn't plain text. | None |
inset | booleanIndents the label to line up with items that have an icon or indicator. | false |
onSelect | (event: Event) => voidCalled on click, Enter or Space. The menu closes unless you call event.preventDefault(). | None |
DropdownMenuCheckboxItem
A role="menuitemcheckbox" with a lit check box. Takes the item props above except icon and variant.
| Prop | Type | Default |
|---|---|---|
checked | booleanControlled state. | None |
defaultChecked | booleanInitial state when uncontrolled. | false |
onCheckedChange | (checked: boolean) => voidCalled with the new state on select. | None |
shortcut | ReactNodeRight-aligned hint. | None |
DropdownMenuRadioGroup
A role="group" whose radio items share one value.
| Prop | Type | Default |
|---|---|---|
value | stringControlled value. | None |
defaultValue | stringInitial value when uncontrolled. | None |
onValueChange | (value: string) => voidCalled when a different radio item is selected. | None |
DropdownMenuRadioItem
A role="menuitemradio". Takes the item props above except icon and variant.
| Prop | Type | Default |
|---|---|---|
value | stringThe value this item selects. | None |
shortcut | ReactNodeRight-aligned hint. | None |
DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuSubContent
A nested menu. Sub holds the state, the trigger is an item that opens it, the content is a second portaled panel.
| Prop | Type | Default |
|---|---|---|
DropdownMenuSubTrigger icon | ReactNodeLeading icon. Also takes disabled, textValue and inset. | None |
DropdownMenuSubContent sideOffset | numberGap from the parent menu in px. Opens to the right, or the left when there's no room. | 2 |
DropdownMenuLabel, DropdownMenuGroup, DropdownMenuSeparator
Structure. Each accepts the native attributes of its div.
| Prop | Type | Default |
|---|---|---|
DropdownMenuLabel inset | booleanLines the label up with items that have an icon or indicator. | false |
DropdownMenuGroup | divrole="group", labelled by a DropdownMenuLabel inside it. | None |
DropdownMenuSeparator | divrole="separator" hairline. | None |
DropdownMenuAnchor
Optional. Positions the menu against this element instead of the trigger and lends it its color. Context Menu uses it for the right-click area.
| Prop | Type | Default |
|---|---|---|
asChild | booleanUse the single child as the anchor instead of rendering a div. | false |
Accessibility
- Follows the WAI-ARIA menu button pattern: the trigger has aria-haspopup="menu" and aria-expanded, items are menuitem, menuitemcheckbox or menuitemradio with real DOM focus.
- Enter, Space and ArrowDown on the trigger open the menu on the first item; ArrowUp opens it on the last. A mouse opens it on press, so press, drag and release selects.
- Arrow keys move and skip disabled items, Home and End jump, and typing letters jumps to a matching item. Pressing the same letter again cycles.
- Enter or Space selects. ArrowRight or Enter opens a submenu on its first item and ArrowLeft closes it. Escape closes one level at a time.
- Tab closes the menu and moves on from the trigger. Escape, a selection and Tab return focus to the trigger.
- When the trigger sits in a modal Dialog, the menu renders inside the dialog so it isn't made inert.