Dropdown Menu
A floating menu of actions with submenus, checkbox and radio items; the highlighted row presses momentarily into the surface.
@emboss/dropdown-menu
Installation
pnpm dlx shadcn@latest add @emboss/dropdown-menuUsage
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
export function Example() {
return (
<DropdownMenu>
<DropdownMenuTrigger render={<Button>Options</Button>} />
<DropdownMenuContent>
<DropdownMenuItem>Duplicate</DropdownMenuItem>
<DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}API reference
DropdownMenu / DropdownMenuTrigger / DropdownMenuContent
Root state, trigger, and the floating list.
| Prop | Type | Default | Description |
|---|---|---|---|
| sideOffset | number | 6 | Gap between trigger and menu (DropdownMenuContent). |
DropdownMenuItem
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "default" | "destructive" | "default" | Destructive items carry danger ink. |
| onClick | MouseEventHandler | — | Runs when the item is activated. |
| Data attribute | Description |
|---|---|
| data-highlighted | Present on the virtually focused item. |
DropdownMenuCheckboxItem / DropdownMenuRadioGroup / DropdownMenuRadioItem
Stateful items with check and radio indicators.
| Prop | Type | Default | Description |
|---|---|---|---|
| checked / defaultChecked / onCheckedChange | boolean / boolean / (checked) => void | — | Checkbox item state (checkbox items). |
DropdownMenuLabel / DropdownMenuSeparator / DropdownMenuGroup / DropdownMenuSub*
Engraved group labels, machined separators, grouping, and submenu parts.
Keyboard
| Keys | Action |
|---|---|
| ArrowDownArrowUp | Moves the highlight through the menu. |
| Enter | Activates the highlighted item. |
| ArrowRight | Opens a submenu. |
| Esc | Closes the menu and returns focus. |
Source
View source — dropdown-menu.tsxHide source — dropdown-menu.tsx
"use client";
import { Menu as BaseMenu } from "@base-ui/react/menu";
import { Check, ChevronRight, Circle } from "lucide-react";
import { cn } from "@/lib/utils";
const itemClassName =
"grid cursor-pointer grid-cols-[1rem_1fr_auto] items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-highlighted:bg-well data-highlighted:shadow-deboss-1 data-[variant=destructive]:text-danger";
const popupClassName =
"min-w-44 origin-(--transform-origin) rounded-md bg-surface-2 p-1 text-ink shadow-float-1 transition-[opacity,scale] duration-(--duration-press) ease-(--ease-press) data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0";
/**
* A floating menu of actions. The highlighted item momentarily presses into
* the surface; destructive items carry danger ink.
*
* @example
* <DropdownMenu>
* <DropdownMenuTrigger render={<Button>Channel</Button>} />
* <DropdownMenuContent>
* <DropdownMenuItem onClick={duplicate}>Duplicate</DropdownMenuItem>
* <DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
* </DropdownMenuContent>
* </DropdownMenu>
*/
function DropdownMenu(props: React.ComponentProps<typeof BaseMenu.Root>) {
return <BaseMenu.Root {...props} />;
}
function DropdownMenuTrigger(
props: React.ComponentProps<typeof BaseMenu.Trigger>,
) {
return <BaseMenu.Trigger data-slot="dropdown-menu-trigger" {...props} />;
}
function DropdownMenuContent({
className,
sideOffset = 6,
...props
}: React.ComponentProps<typeof BaseMenu.Popup> & { sideOffset?: number }) {
return (
<BaseMenu.Portal>
<BaseMenu.Positioner sideOffset={sideOffset} className="z-50">
<BaseMenu.Popup
data-slot="dropdown-menu-content"
className={cn(popupClassName, className)}
{...props}
/>
</BaseMenu.Positioner>
</BaseMenu.Portal>
);
}
function DropdownMenuItem({
className,
variant,
...props
}: React.ComponentProps<typeof BaseMenu.Item> & {
/** Destructive items carry danger ink. */
variant?: "default" | "destructive";
}) {
return (
<BaseMenu.Item
data-slot="dropdown-menu-item"
data-variant={variant ?? "default"}
className={cn(itemClassName, "grid-cols-1", className)}
{...props}
/>
);
}
function DropdownMenuCheckboxItem({
className,
children,
...props
}: React.ComponentProps<typeof BaseMenu.CheckboxItem>) {
return (
<BaseMenu.CheckboxItem
data-slot="dropdown-menu-checkbox-item"
className={cn(itemClassName, className)}
{...props}
>
<BaseMenu.CheckboxItemIndicator className="col-start-1 flex">
<Check className="size-3.5" aria-hidden />
</BaseMenu.CheckboxItemIndicator>
<span className="col-start-2">{children}</span>
</BaseMenu.CheckboxItem>
);
}
function DropdownMenuRadioGroup(
props: React.ComponentProps<typeof BaseMenu.RadioGroup>,
) {
return (
<BaseMenu.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />
);
}
function DropdownMenuRadioItem({
className,
children,
...props
}: React.ComponentProps<typeof BaseMenu.RadioItem>) {
return (
<BaseMenu.RadioItem
data-slot="dropdown-menu-radio-item"
className={cn(itemClassName, className)}
{...props}
>
<BaseMenu.RadioItemIndicator className="col-start-1 flex">
<Circle className="size-2 fill-current" aria-hidden />
</BaseMenu.RadioItemIndicator>
<span className="col-start-2">{children}</span>
</BaseMenu.RadioItem>
);
}
function DropdownMenuGroup(props: React.ComponentProps<typeof BaseMenu.Group>) {
return <BaseMenu.Group data-slot="dropdown-menu-group" {...props} />;
}
function DropdownMenuLabel({
className,
...props
}: React.ComponentProps<typeof BaseMenu.GroupLabel>) {
return (
<BaseMenu.GroupLabel
data-slot="dropdown-menu-label"
className={cn(
"tracking-label px-2 py-1.5 font-mono text-label text-ink-faint uppercase text-engraved",
className,
)}
{...props}
/>
);
}
function DropdownMenuSeparator({
className,
...props
}: React.ComponentProps<typeof BaseMenu.Separator>) {
return (
<BaseMenu.Separator
data-slot="dropdown-menu-separator"
className={cn("-mx-1 my-1 h-px bg-edge-line", className)}
{...props}
/>
);
}
function DropdownMenuSub(
props: React.ComponentProps<typeof BaseMenu.SubmenuRoot>,
) {
return <BaseMenu.SubmenuRoot {...props} />;
}
function DropdownMenuSubTrigger({
className,
children,
...props
}: React.ComponentProps<typeof BaseMenu.SubmenuTrigger>) {
return (
<BaseMenu.SubmenuTrigger
data-slot="dropdown-menu-sub-trigger"
className={cn(itemClassName, "grid-cols-[1fr_auto]", className)}
{...props}
>
<span>{children}</span>
<ChevronRight className="size-3.5 text-ink-faint" aria-hidden />
</BaseMenu.SubmenuTrigger>
);
}
function DropdownMenuSubContent({
className,
...props
}: React.ComponentProps<typeof BaseMenu.Popup>) {
return (
<BaseMenu.Portal>
<BaseMenu.Positioner className="z-50">
<BaseMenu.Popup
data-slot="dropdown-menu-sub-content"
className={cn(popupClassName, className)}
{...props}
/>
</BaseMenu.Positioner>
</BaseMenu.Portal>
);
}
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
};