Dropdown Menu
A button-triggered menu with items, checkboxes, radio groups, and submenus.
import * as DropdownMenu from '@gentleduck/primitives/dropdown-menu'import * as DropdownMenu from '@gentleduck/primitives/dropdown-menu'Anatomy
<DropdownMenu.Root>
<DropdownMenu.Trigger />
<DropdownMenu.Portal>
<DropdownMenu.Content>
<DropdownMenu.Label />
<DropdownMenu.Item />
<DropdownMenu.Group>
<DropdownMenu.Item />
</DropdownMenu.Group>
<DropdownMenu.CheckboxItem>
<DropdownMenu.ItemIndicator />
</DropdownMenu.CheckboxItem>
<DropdownMenu.RadioGroup>
<DropdownMenu.RadioItem>
<DropdownMenu.ItemIndicator />
</DropdownMenu.RadioItem>
</DropdownMenu.RadioGroup>
<DropdownMenu.Separator />
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger />
<DropdownMenu.SubContent />
</DropdownMenu.Sub>
<DropdownMenu.Arrow />
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root><DropdownMenu.Root>
<DropdownMenu.Trigger />
<DropdownMenu.Portal>
<DropdownMenu.Content>
<DropdownMenu.Label />
<DropdownMenu.Item />
<DropdownMenu.Group>
<DropdownMenu.Item />
</DropdownMenu.Group>
<DropdownMenu.CheckboxItem>
<DropdownMenu.ItemIndicator />
</DropdownMenu.CheckboxItem>
<DropdownMenu.RadioGroup>
<DropdownMenu.RadioItem>
<DropdownMenu.ItemIndicator />
</DropdownMenu.RadioItem>
</DropdownMenu.RadioGroup>
<DropdownMenu.Separator />
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger />
<DropdownMenu.SubContent />
</DropdownMenu.Sub>
<DropdownMenu.Arrow />
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>Example
import * as DropdownMenu from '@gentleduck/primitives/dropdown-menu'
function ActionsMenu() {
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger className="px-3 py-2 border rounded">
Actions
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content className="bg-white shadow-lg rounded-md p-1 min-w-[180px] border" sideOffset={4}>
<DropdownMenu.Item className="px-3 py-1.5 rounded hover:bg-gray-100 cursor-pointer">
Edit
</DropdownMenu.Item>
<DropdownMenu.Item className="px-3 py-1.5 rounded hover:bg-gray-100 cursor-pointer">
Duplicate
</DropdownMenu.Item>
<DropdownMenu.Separator className="h-px bg-gray-200 my-1" />
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger className="px-3 py-1.5 rounded hover:bg-gray-100 cursor-pointer flex justify-between">
Share <span>></span>
</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent className="bg-white shadow-lg rounded-md p-1 min-w-[140px] border">
<DropdownMenu.Item className="px-3 py-1.5 rounded hover:bg-gray-100 cursor-pointer">
Copy Link
</DropdownMenu.Item>
<DropdownMenu.Item className="px-3 py-1.5 rounded hover:bg-gray-100 cursor-pointer">
Email
</DropdownMenu.Item>
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
<DropdownMenu.Separator className="h-px bg-gray-200 my-1" />
<DropdownMenu.Item className="px-3 py-1.5 rounded hover:bg-gray-100 cursor-pointer text-red-600">
Delete
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
)
}import * as DropdownMenu from '@gentleduck/primitives/dropdown-menu'
function ActionsMenu() {
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger className="px-3 py-2 border rounded">
Actions
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content className="bg-white shadow-lg rounded-md p-1 min-w-[180px] border" sideOffset={4}>
<DropdownMenu.Item className="px-3 py-1.5 rounded hover:bg-gray-100 cursor-pointer">
Edit
</DropdownMenu.Item>
<DropdownMenu.Item className="px-3 py-1.5 rounded hover:bg-gray-100 cursor-pointer">
Duplicate
</DropdownMenu.Item>
<DropdownMenu.Separator className="h-px bg-gray-200 my-1" />
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger className="px-3 py-1.5 rounded hover:bg-gray-100 cursor-pointer flex justify-between">
Share <span>></span>
</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent className="bg-white shadow-lg rounded-md p-1 min-w-[140px] border">
<DropdownMenu.Item className="px-3 py-1.5 rounded hover:bg-gray-100 cursor-pointer">
Copy Link
</DropdownMenu.Item>
<DropdownMenu.Item className="px-3 py-1.5 rounded hover:bg-gray-100 cursor-pointer">
Email
</DropdownMenu.Item>
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
<DropdownMenu.Separator className="h-px bg-gray-200 my-1" />
<DropdownMenu.Item className="px-3 py-1.5 rounded hover:bg-gray-100 cursor-pointer text-red-600">
Delete
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
)
}API
DropdownMenu.Root
The root component that manages open/closed state and provides context. Wraps the base Menu primitive internally.
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | -- | Controlled open state |
defaultOpen | boolean | false | Initial open state (uncontrolled) |
onOpenChange | (open: boolean) => void | -- | Called when the open state should change |
dir | 'ltr' | 'rtl' | -- | Text direction. Resolved with useDirection (dir prop -> DirectionProvider -> 'ltr'). |
modal | boolean | true | When true, interaction with outside elements is disabled and only menu content is visible to screen readers |
DropdownMenu.Trigger
Button that toggles the dropdown menu. Renders a <button> with aria-haspopup="menu".
| Prop | Type | Description |
|---|---|---|
asChild | boolean | Render as the child element instead of a <button> |
disabled | boolean | Disables the trigger |
Sets aria-expanded, aria-controls, and data-state automatically.
DropdownMenu.Portal
Renders the dropdown content into a portal.
| Prop | Type | Default | Description |
|---|---|---|---|
container | Element | null | document.body | Portal target |
DropdownMenu.Content
The dropdown content. Handles focus management, keyboard navigation, and dismiss behavior.
| Prop | Type | Default | Description |
|---|---|---|---|
side | 'top' | 'right' | 'bottom' | 'left' | 'bottom' | Preferred side relative to the trigger |
sideOffset | number | -- | Main-axis offset from trigger |
align | 'start' | 'center' | 'end' | -- | Cross-axis alignment |
alignOffset | number | -- | Cross-axis offset |
avoidCollisions | boolean | true | Flip to avoid viewport overflow |
collisionPadding | number | -- | Padding from viewport edges |
onCloseAutoFocus | (event: Event) => void | -- | Called when focus returns to trigger on close |
onEscapeKeyDown | (event: KeyboardEvent) => void | -- | Called when Escape is pressed |
onPointerDownOutside | (event) => void | -- | Called when clicking outside |
onInteractOutside | (event) => void | -- | Called on any interaction outside |
Exposes data-state="open" / data-state="closed" and data-side for CSS animation.
CSS custom properties available:
--gentleduck-dropdown-menu-content-transform-origin--gentleduck-dropdown-menu-content-available-width--gentleduck-dropdown-menu-content-available-height--gentleduck-dropdown-menu-trigger-width--gentleduck-dropdown-menu-trigger-height
DropdownMenu.Group
Groups related items. Renders a <div> with role="group".
DropdownMenu.Label
Non-interactive label for a group.
DropdownMenu.Item
An interactive menu item. Fires onSelect when activated.
| Prop | Type | Description |
|---|---|---|
disabled | boolean | Disable the item |
onSelect | (event: Event) => void | Called when item is selected |
textValue | string | Text override for typeahead search |
Exposes data-highlighted when focused and data-disabled when disabled.
DropdownMenu.CheckboxItem
A toggleable menu item with checked state.
| Prop | Type | Description |
|---|---|---|
checked | boolean | 'indeterminate' | Checked state |
onCheckedChange | (checked: boolean) => void | Called on toggle |
disabled | boolean | Disable the item |
DropdownMenu.RadioGroup / DropdownMenu.RadioItem
Mutually exclusive menu items.
| Prop (RadioGroup) | Type | Description |
|---|---|---|
value | string | Currently selected value |
onValueChange | (value: string) => void | Called when selection changes |
| Prop (RadioItem) | Type | Description |
|---|---|---|
value | string | Value for this item |
disabled | boolean | Disable the item |
DropdownMenu.ItemIndicator
Renders only when the parent item is checked. Use for check marks or radio dots.
| Prop | Type | Description |
|---|---|---|
forceMount | boolean | Keep mounted for animation control |
DropdownMenu.Sub / DropdownMenu.SubTrigger / DropdownMenu.SubContent
Nested submenus.
| Prop (Sub) | Type | Default | Description |
|---|---|---|---|
open | boolean | -- | Controlled open state |
defaultOpen | boolean | false | Initial open state |
onOpenChange | (open: boolean) => void | -- | Called when open state changes |
SubContent exposes the same --gentleduck-dropdown-menu-* CSS custom properties as Content.
DropdownMenu.Separator
Visual separator between groups. Renders a <div> with aria-hidden.
DropdownMenu.Arrow
Arrow pointing to the trigger.
| Prop | Type | Default | Description |
|---|---|---|---|
width | number | 10 | Arrow width in pixels |
height | number | 5 | Arrow height in pixels |
Keyboard interactions
| Key | Action |
|---|---|
| Space / Enter | Opens menu (on trigger) or activates highlighted item |
| ArrowDown | Opens menu or highlights next item |
| ArrowUp | Highlights previous item |
| ArrowRight | Opens submenu (on SubTrigger) |
| ArrowLeft | Closes submenu |
| Escape | Closes menu |