import {
CreditCardIcon,
KeyboardIcon,
LifeBuoyIcon,
LogOutIcon,
MoonIcon,
SettingsIcon,
SunIcon,
SunMoonIcon,
UserIcon,
UsersIcon,
} from "lucide-react";
import * as React from "react";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
export default function DropdownMenuAccount() {
const [theme, setTheme] = React.useState("system");
return (
<DropdownMenu>
<DropdownMenuTrigger render={<Button variant="ghost" className="pl-1" />}>
<Avatar size="sm">
<AvatarFallback>AP</AvatarFallback>
</Avatar>
Ada Park
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuGroup>
<DropdownMenuLabel>ada@example.com</DropdownMenuLabel>
<DropdownMenuItem>
<UserIcon />
Profile
<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<CreditCardIcon />
Billing
</DropdownMenuItem>
<DropdownMenuItem>
<SettingsIcon />
Settings
<DropdownMenuShortcut>⌘,</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<SunMoonIcon />
Theme
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuRadioGroup value={theme} onValueChange={setTheme}>
<DropdownMenuRadioItem value="light">
<SunIcon />
Light
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="dark">
<MoonIcon />
Dark
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="system">
<SunMoonIcon />
System
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuSubContent>
</DropdownMenuSub>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<UsersIcon />
Invite people
</DropdownMenuItem>
<DropdownMenuItem>
<LifeBuoyIcon />
Help
</DropdownMenuItem>
<DropdownMenuItem disabled>
<KeyboardIcon />
Shortcuts
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
<LogOutIcon />
Sign out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}- Add the registryOnce per project
pnpm dlx shadcn@latest registry add @jarvis=https://ui.jarv.is/r/{name}.json - Add dropdown-menu
pnpm dlx shadcn@latest add @jarvis/dropdown-menu
Usage
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
export function FileMenu() {
return (
<DropdownMenu>
<DropdownMenuTrigger render={<Button variant="secondary" />}>File</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuGroup>
<DropdownMenuLabel>Document</DropdownMenuLabel>
<DropdownMenuItem>
New
<DropdownMenuShortcut>⌘N</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>Rename</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}Handle an item with onClick. DropdownMenuShortcut only displays a shortcut, so bind the keys
yourself. Keep each DropdownMenuLabel inside a DropdownMenuGroup.
Items also take variant="destructive" and disabled.
Composition
Examples
Icon trigger
Give an icon-only trigger an aria-label. align="end" lines the menu up with its right edge.
import { ArchiveIcon, CopyIcon, EllipsisIcon, PencilIcon, Trash2Icon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
export default function DropdownMenuIconTrigger() {
return (
<DropdownMenu>
<DropdownMenuTrigger
render={<Button variant="ghost" size="icon" aria-label="More actions" />}
>
<EllipsisIcon />
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>
<PencilIcon />
Rename
</DropdownMenuItem>
<DropdownMenuItem>
<CopyIcon />
Duplicate
</DropdownMenuItem>
<DropdownMenuItem>
<ArchiveIcon />
Archive
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
<Trash2Icon />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}Checkbox items
DropdownMenuCheckboxItem toggles a setting with checked and onCheckedChange.
import { ListFilterIcon } from "lucide-react";
import * as React from "react";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
export default function DropdownMenuCheckboxItems() {
const [showCompleted, setShowCompleted] = React.useState(true);
const [showArchived, setShowArchived] = React.useState(false);
return (
<DropdownMenu>
<DropdownMenuTrigger render={<Button variant="secondary" />}>
<ListFilterIcon />
View
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuGroup>
<DropdownMenuLabel>Show</DropdownMenuLabel>
<DropdownMenuCheckboxItem checked={showCompleted} onCheckedChange={setShowCompleted}>
Completed tasks
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem checked={showArchived} onCheckedChange={setShowArchived}>
Archived tasks
</DropdownMenuCheckboxItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
);
}Radio items
Wrap DropdownMenuRadioItems in a DropdownMenuRadioGroup to pick one of several options.
import { ArrowUpDownIcon } from "lucide-react";
import * as React from "react";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
export default function DropdownMenuRadioItems() {
const [sort, setSort] = React.useState("due");
return (
<DropdownMenu>
<DropdownMenuTrigger render={<Button variant="secondary" />}>
<ArrowUpDownIcon />
Sort
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuGroup>
<DropdownMenuLabel>Sort by</DropdownMenuLabel>
<DropdownMenuRadioGroup value={sort} onValueChange={setSort}>
<DropdownMenuRadioItem value="due">Due date</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="priority">Priority</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="updated">Last updated</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
);
}Submenus
Nest a menu with DropdownMenuSub, DropdownMenuSubTrigger and DropdownMenuSubContent.
import { CopyIcon, FolderIcon, FolderInputIcon, PencilIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
const projects = ["Website redesign", "Mobile app", "Brand guidelines"];
export default function DropdownMenuSubmenu() {
return (
<DropdownMenu>
<DropdownMenuTrigger render={<Button variant="secondary" />}>Task</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>
<PencilIcon />
Edit
</DropdownMenuItem>
<DropdownMenuItem>
<CopyIcon />
Duplicate
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<FolderInputIcon />
Move to
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
{projects.map((project) => (
<DropdownMenuItem key={project}>
<FolderIcon />
{project}
</DropdownMenuItem>
))}
</DropdownMenuSubContent>
</DropdownMenuSub>
</DropdownMenuContent>
</DropdownMenu>
);
}Props
Items, labels and submenu triggers take inset to line up with items that have an icon.
The tables leave out className, style and render. Parts built on Base UI take them as its styling and composition guides describe.
DropdownMenu
| Prop | Type | Default |
|---|---|---|
defaultOpenWhether the menu is initially open. To render a controlled menu, use the | boolean | false |
loopFocusWhether to loop keyboard focus back to the first item when the end of the list is reached while using the arrow keys. | boolean | true |
highlightItemOnHoverWhether moving the pointer over items should highlight them.
Disabling this prop allows CSS | boolean | true |
modalDetermines if the menu enters a modal state when open.
On touch devices, a Nested menus ignore this prop, and menus opened by hover are never modal. | boolean | true |
onOpenChangeEvent handler called when the menu is opened or closed. | ((open: boolean, eventDetails: Menu.Root.ChangeEventDetails) => void) | — |
onOpenChangeCompleteEvent handler called after any animations complete when the menu is opened or closed. | ((open: boolean) => void) | — |
onItemHighlightedCallback fired when an item is highlighted or unhighlighted.
Receives the highlighted item element (or
| ((highlightedItem: HTMLElement | undefined, eventDetails: Menu.Root.HighlightEventDetails) => void) | — |
openWhether the menu is currently open. | boolean | — |
orientationThe visual orientation of the menu. Controls whether roving focus uses up/down or left/right arrow keys. | "horizontal" | "vertical" | 'vertical' |
disabledWhether the component should ignore user interaction. | boolean | false |
closeParentOnEscWhen in a submenu, determines whether pressing the Escape key closes the entire menu, or only the current child menu. | boolean | false |
actionsRefA ref to imperative actions.
| React.RefObject<MenuRootActions | null> | — |
triggerIdID of the trigger that the menu is associated with.
This is useful in conjunction with the | string | null | — |
defaultTriggerIdID of the trigger that the menu is associated with.
This is useful in conjunction with the | string | null | — |
handleA handle to associate the menu with a trigger. If specified, allows external triggers to control the menu's open state. | Menu.Handle<unknown> | — |
childrenThe content of the menu.
This can be a regular React node or a render function that receives the | PayloadChildRenderFunction<unknown> | React.ReactNode | — |
DropdownMenuPortal
| Prop | Type | Default |
|---|---|---|
keepMountedWhether to keep the portal mounted in the DOM while the popup is hidden. | boolean | false |
containerA parent element to render the portal element into. | HTMLElement | React.RefObject<HTMLElement | ShadowRoot | null> | ShadowRoot | null | — |
DropdownMenuTrigger
| Prop | Type | Default |
|---|---|---|
children | React.ReactNode | — |
disabledWhether the component should ignore user interaction. | boolean | false |
handleA handle to associate the trigger with a menu. | Menu.Handle<unknown> | — |
payloadA payload to pass to the menu when it is opened. | unknown | — |
delayHow long to wait before the menu may be opened on hover. Specified in milliseconds. Requires the | number | 100 |
closeDelayHow long to wait before closing the menu that was opened on hover. Specified in milliseconds. Requires the | number | 0 |
openOnHoverWhether the menu should also open when the trigger is hovered. | boolean | — |
nativeButtonWhether the component renders a native | boolean | true |
DropdownMenuContent
| Prop | Type | Default |
|---|---|---|
children | React.ReactNode | — |
id | string | — |
finalFocusDetermines the element to focus when the menu is closed.
| boolean | React.RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | HTMLElement | null | void) | — |
sideOffsetDistance between the anchor and the popup in pixels. Also accepts a function that returns the distance to read the dimensions of the anchor and positioner elements, along with its side and alignment. The function takes a
| number | OffsetFunction | 4 |
alignOffsetAdditional offset along the alignment axis in pixels. Also accepts a function that returns the offset to read the dimensions of the anchor and positioner elements, along with its side and alignment. The function takes a
| number | OffsetFunction | 0 |
alignHow to align the popup relative to the specified side. Submenus and menubars default to | "center" | "end" | "start" | "start" |
sideWhich side of the anchor element to align the popup against. May automatically change to avoid collisions. Submenus and vertical menubars default to | "bottom" | "inline-end" | "inline-start" | "left" | "right" | "top" | "bottom" |
DropdownMenuGroup
| Prop | Type | Default |
|---|---|---|
childrenThe content of the component. | React.ReactNode | — |
DropdownMenuLabel
| Prop | Type | Default |
|---|---|---|
inset | boolean | — |
DropdownMenuItem
| Prop | Type | Default |
|---|---|---|
onClickThe click handler for the menu item. | ((event: BaseUIEvent<React.MouseEvent<HTMLDivElement, MouseEvent>>) => void) | — |
disabledWhether the component should ignore user interaction. | boolean | false |
labelOverrides the text used for keyboard text navigation and filtering. Falls back to the rendered text when not provided. | string | — |
id | string | — |
closeOnClickWhether to close the menu when the item is clicked. | boolean | true |
nativeButtonWhether the component renders a native | boolean | false |
inset | boolean | — |
variant | "default" | "destructive" | "default" |
DropdownMenuSub
| Prop | Type | Default |
|---|---|---|
onOpenChangeEvent handler called when the menu is opened or closed. | ((open: boolean, eventDetails: Menu.SubmenuRoot.ChangeEventDetails) => void) | — |
closeParentOnEscWhen in a submenu, determines whether pressing the Escape key closes the entire menu, or only the current child menu. | boolean | false |
childrenThe content of the submenu. | React.ReactNode | — |
defaultOpenWhether the menu is initially open. To render a controlled menu, use the | boolean | false |
loopFocusWhether to loop keyboard focus back to the first item when the end of the list is reached while using the arrow keys. | boolean | true |
highlightItemOnHoverWhether moving the pointer over items should highlight them.
Disabling this prop allows CSS | boolean | true |
onOpenChangeCompleteEvent handler called after any animations complete when the menu is opened or closed. | ((open: boolean) => void) | — |
onItemHighlightedCallback fired when an item is highlighted or unhighlighted.
Receives the highlighted item element (or
| ((highlightedItem: HTMLElement | undefined, eventDetails: Menu.Root.HighlightEventDetails) => void) | — |
openWhether the menu is currently open. | boolean | — |
orientationThe visual orientation of the menu. Controls whether roving focus uses up/down or left/right arrow keys. | "horizontal" | "vertical" | 'vertical' |
disabledWhether the component should ignore user interaction. | boolean | false |
actionsRefA ref to imperative actions.
| React.RefObject<MenuRootActions | null> | — |
DropdownMenuSubTrigger
| Prop | Type | Default |
|---|---|---|
onClick | ((event: BaseUIEvent<React.MouseEvent<HTMLDivElement, MouseEvent>>) => void) | — |
labelOverrides the text used for keyboard text navigation and filtering. Falls back to the rendered text when not provided. | string | — |
id | string | — |
disabledWhether the component should ignore user interaction. | boolean | false |
delayHow long to wait before the menu may be opened on hover. Specified in milliseconds. Requires the | number | 100 |
closeDelayHow long to wait before closing the menu that was opened on hover. Specified in milliseconds. Requires the | number | 0 |
openOnHoverWhether the menu should also open when the trigger is hovered. | boolean | true |
nativeButtonWhether the component renders a native | boolean | false |
inset | boolean | — |
DropdownMenuSubContent
| Prop | Type | Default |
|---|---|---|
children | React.ReactNode | — |
id | string | — |
finalFocusDetermines the element to focus when the menu is closed.
| boolean | React.RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | HTMLElement | null | void) | — |
sideOffsetDistance between the anchor and the popup in pixels. Also accepts a function that returns the distance to read the dimensions of the anchor and positioner elements, along with its side and alignment. The function takes a
| number | OffsetFunction | 2 |
alignOffsetAdditional offset along the alignment axis in pixels. Also accepts a function that returns the offset to read the dimensions of the anchor and positioner elements, along with its side and alignment. The function takes a
| number | OffsetFunction | -2 |
alignHow to align the popup relative to the specified side. Submenus and menubars default to | "center" | "end" | "start" | "start" |
sideWhich side of the anchor element to align the popup against. May automatically change to avoid collisions. Submenus and vertical menubars default to | "bottom" | "inline-end" | "inline-start" | "left" | "right" | "top" | "right" |
DropdownMenuCheckboxItem
| Prop | Type | Default |
|---|---|---|
checkedWhether the checkbox item is currently ticked. To render an uncontrolled checkbox item, use the | boolean | — |
defaultCheckedWhether the checkbox item is initially ticked. To render a controlled checkbox item, use the | boolean | false |
onCheckedChangeEvent handler called when the checkbox item is ticked or unticked. | ((checked: boolean, eventDetails: Menu.CheckboxItem.ChangeEventDetails) => void) | — |
onClickThe click handler for the menu item. | ((event: BaseUIEvent<React.MouseEvent<HTMLDivElement, MouseEvent>>) => void) | — |
disabledWhether the component should ignore user interaction. | boolean | false |
labelOverrides the text used for keyboard text navigation and filtering. Falls back to the rendered text when not provided. | string | — |
id | string | — |
closeOnClickWhether to close the menu when the item is clicked. | boolean | false |
nativeButtonWhether the component renders a native | boolean | false |
inset | boolean | — |
DropdownMenuRadioGroup
| Prop | Type | Default |
|---|---|---|
childrenThe content of the component. | React.ReactNode | — |
valueThe controlled value of the radio item that should be currently selected. To render an uncontrolled radio group, use the | any | — |
defaultValueThe uncontrolled value of the radio item that should be initially selected. To render a controlled radio group, use the | any | — |
onValueChangeFunction called when the selected value changes. | ((value: any, eventDetails: Menu.RadioGroup.ChangeEventDetails) => void) | — |
disabledWhether the component should ignore user interaction. | boolean | false |
DropdownMenuRadioItem
| Prop | Type | Default |
|---|---|---|
valueValue of the radio item. This is the value that will be set in the MenuRadioGroup when the item is selected. | any | — |
onClickThe click handler for the menu item. | ((event: BaseUIEvent<React.MouseEvent<HTMLDivElement, MouseEvent>>) => void) | — |
disabledWhether the component should ignore user interaction. | boolean | false |
labelOverrides the text used for keyboard text navigation and filtering. Falls back to the rendered text when not provided. | string | — |
id | string | — |
closeOnClickWhether to close the menu when the item is clicked. | boolean | false |
nativeButtonWhether the component renders a native | boolean | false |
inset | boolean | — |
DropdownMenuSeparator
| Prop | Type | Default |
|---|---|---|
orientationThe orientation of the separator. | "horizontal" | "vertical" | 'horizontal' |