Dropdown Menu

Preview

import { Button } from "@/registry/components/button/react/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/registry/components/dropdown-menu/react/dropdown-menu";
export function DropdownMenuDemo() {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button data-variant="outline">Open</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuGroup>
<DropdownMenuItem>
Profile
<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
Billing
<DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
Settings
<DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
Keyboard shortcuts
<DropdownMenuShortcut>⌘K</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>Team</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>Invite users</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>Email</DropdownMenuItem>
<DropdownMenuItem>Message</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>More...</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuItem>
New Team
<DropdownMenuShortcut>⌘+T</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>GitHub</DropdownMenuItem>
<DropdownMenuItem>Support</DropdownMenuItem>
<DropdownMenuItem disabled>API</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
Log out
<DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}

Installation

You can add the dropdown menu component in two ways:

  1. During project initialization:
pnpm dlx make-sugarcube@latest init
npx make-sugarcube@latest init
yarn make-sugarcube@latest init
bunx --bun make-sugarcube@latest init

Then select the dropdown menu component when prompted.

  1. After initialization:
pnpm dlx make-sugarcube@latest components dropdown-menu --framework react
npx make-sugarcube@latest components dropdown-menu --framework react
yarn make-sugarcube@latest components dropdown-menu --framework react
bunx --bun make-sugarcube@latest components dropdown-menu --framework react

What’s included

When you add the dropdown menu component, you get:

  • dropdown-menu.tsx - The React component implementation
  • dropdown-menu.css - The base styles for the dropdown menu
  • @clsx - The clsx utility for conditional classes
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import cn from "clsx";
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
function DropdownMenu({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
}
function DropdownMenuPortal({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
return <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;
}
function DropdownMenuTrigger({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
return <DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />;
}
function DropdownMenuContent({
className,
sideOffset = 4,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
return (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
data-slot="dropdown-menu-content"
sideOffset={sideOffset}
className={cn("dropdown-menu-content", className)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
);
}
function DropdownMenuGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
return <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
}
function DropdownMenuItem({
className,
inset,
variant = "default",
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean;
variant?: "default" | "destructive";
}) {
return (
<DropdownMenuPrimitive.Item
data-slot="dropdown-menu-item"
data-inset={inset}
data-variant={variant}
className={cn("dropdown-menu-item", className)}
{...props}
/>
);
}
function DropdownMenuCheckboxItem({
className,
children,
checked,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem> & {
checked?: boolean;
}) {
return (
<DropdownMenuPrimitive.CheckboxItem
data-slot="dropdown-menu-checkbox-item"
className={cn("dropdown-menu-item", className)}
{...(checked !== undefined && { checked })}
{...props}
>
<span className="dropdown-menu-item-indicator">
<DropdownMenuPrimitive.ItemIndicator>
<CheckIcon className="check" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
);
}
function DropdownMenuRadioGroup({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
return <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />;
}
function DropdownMenuRadioItem({
className,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
return (
<DropdownMenuPrimitive.RadioItem
data-slot="dropdown-menu-radio-item"
className={cn("dropdown-menu-item", className)}
{...props}
>
<span className="dropdown-menu-item-indicator">
<DropdownMenuPrimitive.ItemIndicator>
<CircleIcon />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
);
}
function DropdownMenuLabel({
className,
inset,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean;
}) {
return (
<DropdownMenuPrimitive.Label
data-slot="dropdown-menu-label"
data-inset={inset}
className={cn("dropdown-menu-label", className)}
{...props}
/>
);
}
function DropdownMenuSeparator({
className,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
return (
<DropdownMenuPrimitive.Separator
data-slot="dropdown-menu-separator"
className={cn("dropdown-menu-separator", className)}
{...props}
/>
);
}
function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">) {
return <span className={cn("dropdown-menu-shortcut", className)} {...props} />;
}
function DropdownMenuSub({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
}
function DropdownMenuSubTrigger({
className,
inset,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean;
}) {
return (
<DropdownMenuPrimitive.SubTrigger
data-inset={inset}
className={cn("dropdown-menu-item", className)}
{...props}
>
{children}
<ChevronRightIcon className="" />
</DropdownMenuPrimitive.SubTrigger>
);
}
function DropdownMenuSubContent({
className,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
return (
<DropdownMenuPrimitive.SubContent
data-slot="dropdown-menu-sub-content"
className={cn("dropdown-menu-content", className)}
style={{ "--dropdown-width": 0 } as React.CSSProperties}
{...props}
/>
);
}
export {
DropdownMenu,
DropdownMenuPortal,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
};
.dropdown-menu-content {
overflow-x: hidden;
overflow-y: auto;
min-width: 8rem;
background-color: var(--dropdown-bg);
color: var(--dropdown-fg);
width: var(--dropdown-width);
border-radius: var(--dropdown-radius);
border-style: var(--dropdown-border-style);
border-width: var(--dropdown-border-width);
border-color: var(--dropdown-border-color);
padding: var(--dropdown-padding);
box-shadow: var(--dropdown-shadow);
transform-origin: var(--radix-dropdown-menu-content-transform-origin);
max-height: var(--radix-dropdown-menu-content-available-height);
}
.dropdown-menu-item {
position: relative;
display: flex;
cursor: default;
align-items: center;
justify-content: space-between;
user-select: none;
outline: none;
gap: var(--dropdown-item-gap);
border-radius: var(--dropdown-item-radius);
padding-block: var(--dropdown-item-padding-block);
padding-inline: var(--dropdown-item-padding-inline);
font-size: var(--dropdown-item-font-size);
transition: background-color var(--dropdown-item-transition-duration)
var(--dropdown-item-transition-ease);
&svg {
pointer-events: none;
}
}
.dropdown-menu-item:hover {
background-color: var(--dropdown-item-bg-hover);
}
.dropdown-menu-item[data-disabled] {
color: var(--text-disabled);
pointer-events: none;
}
[data-slot="dropdown-menu-checkbox-item"],
[data-slot="dropdown-menu-radio-item"] {
padding-inline-start: 32px;
}
.dropdown-menu-item-indicator {
position: absolute;
left: 8px;
pointer-events: none;
display: flex;
align-items: center;
justify-content: center;
}
[data-slot="dropdown-menu-radio-item"] .dropdown-menu-item-indicator svg {
width: calc(var(--dropdown-item-indicator-size) / 2);
height: calc(var(--dropdown-item-indicator-size) / 2);
fill: currentColor;
}
.dropdown-menu-label {
padding-block: var(--dropdown-label-padding-block);
padding-inline: var(--dropdown-label-padding-inline);
font-size: var(--dropdown-label-font-size);
font-weight: var(--dropdown-label-font-weight);
}
.dropdown-menu-separator {
height: var(--dropdown-separator-height);
background-color: var(--dropdown-separator-bg);
margin-block: var(--space-3xs);
margin-inline: calc(-1 * var(--dropdown-padding));
}
.dropdown-menu-shortcut {
color: var(--text-muted);
letter-spacing: var(--tracking-wide);
}
[data-slot="dropdown-menu-content"][data-state="open"][data-side="bottom"],
[data-slot="dropdown-menu-sub-content"][data-state="open"][data-side="bottom"] {
animation: dropdown-open-bottom var(--duration-150) var(--ease-out);
}
[data-slot="dropdown-menu-content"][data-state="open"][data-side="top"],
[data-slot="dropdown-menu-sub-content"][data-state="open"][data-side="top"] {
animation: dropdown-open-top var(--duration-150) var(--ease-out);
}
[data-slot="dropdown-menu-content"][data-state="open"][data-side="left"],
[data-slot="dropdown-menu-sub-content"][data-state="open"][data-side="left"] {
animation: dropdown-open-left var(--duration-150) var(--ease-out);
}
[data-slot="dropdown-menu-content"][data-state="open"][data-side="right"],
[data-slot="dropdown-menu-sub-content"][data-state="open"][data-side="right"] {
animation: dropdown-open-right var(--duration-150) var(--ease-out);
}
[data-slot="dropdown-menu-content"][data-state="closed"][data-side="bottom"],
[data-slot="dropdown-menu-sub-content"][data-state="closed"][data-side="bottom"] {
animation: dropdown-closed-bottom var(--duration-150) var(--ease-out);
}
[data-slot="dropdown-menu-content"][data-state="closed"][data-side="top"],
[data-slot="dropdown-menu-sub-content"][data-state="closed"][data-side="top"] {
animation: dropdown-closed-top var(--duration-150) var(--ease-out);
}
[data-slot="dropdown-menu-content"][data-state="closed"][data-side="left"],
[data-slot="dropdown-menu-sub-content"][data-state="closed"][data-side="left"] {
animation: dropdown-closed-left var(--duration-150) var(--ease-out);
}
[data-slot="dropdown-menu-content"][data-state="closed"][data-side="right"],
[data-slot="dropdown-menu-sub-content"][data-state="closed"][data-side="right"] {
animation: dropdown-closed-right var(--duration-150) var(--ease-out);
}
@keyframes dropdown-open-bottom {
from {
opacity: 0;
transform: scale(0.95) translateY(-8px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
@keyframes dropdown-open-top {
from {
opacity: 0;
transform: scale(0.95) translateY(8px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
@keyframes dropdown-open-left {
from {
opacity: 0;
transform: scale(0.95) translateX(8px);
}
to {
opacity: 1;
transform: scale(1) translateX(0);
}
}
@keyframes dropdown-open-right {
from {
opacity: 0;
transform: scale(0.95) translateX(-8px);
}
to {
opacity: 1;
transform: scale(1) translateX(0);
}
}
@keyframes dropdown-closed-bottom {
from {
opacity: 1;
transform: scale(1) translateY(0);
}
to {
opacity: 0;
transform: scale(0.95) translateY(-8px);
}
}
@keyframes dropdown-closed-top {
from {
opacity: 1;
transform: scale(1) translateY(0);
}
to {
opacity: 0;
transform: scale(0.95) translateY(8px);
}
}
@keyframes dropdown-closed-left {
from {
opacity: 1;
transform: scale(1) translateX(0);
}
to {
opacity: 0;
transform: scale(0.95) translateX(-8px);
}
}
@keyframes dropdown-closed-right {
from {
opacity: 1;
transform: scale(1) translateX(0);
}
to {
opacity: 0;
transform: scale(0.95) translateX(8px);
}
}

Examples

:::note The following examples show how to use data attributes for variants. This is just one possible approach. See the principles section for more information on how to approach component variants. :::

import * as React from "react";
import { Button } from "@/registry/components/button/react/button.tsx";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/components/dropdown-menu/react/dropdown-menu.tsx";
export default function DropdownMenuCheckboxes() {
const [showStatusBar, setShowStatusBar] = React.useState<boolean>(true);
const [showActivityBar, setShowActivityBar] = React.useState<boolean>(false);
const [showPanel, setShowPanel] = React.useState<boolean>(false);
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button data-variant="outline">Open</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuLabel>Appearance</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuCheckboxItem
checked={showStatusBar}
onCheckedChange={setShowStatusBar}
>
Status Bar
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem
checked={showActivityBar}
onCheckedChange={setShowActivityBar}
disabled
>
Activity Bar
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem checked={showPanel} onCheckedChange={setShowPanel}>
Panel
</DropdownMenuCheckboxItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
"use client";
import * as React from "react";
import { Button } from "@/registry/components/button/react/button.tsx";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/components/dropdown-menu/react/dropdown-menu.tsx";
export function DropdownMenuRadioGroupDemo() {
const [position, setPosition] = React.useState("bottom");
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button data-variant="outline">Open</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuLabel>Panel Position</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuRadioGroup value={position} onValueChange={setPosition}>
<DropdownMenuRadioItem value="top">Top</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="bottom">Bottom</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="right">Right</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
);
}