Components
Displays a menu to the user — such as a set of actions or functions — triggered by a button.
Playground
min-w-32), so give it an explicit width like w-56 when the trigger is small.import { Button } from "@workspace/ui/components/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from "@workspace/ui/components/dropdown-menu"
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">Open</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuGroup>
<DropdownMenuItem>
Profile <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
Billing <DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
Settings <DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem disabled>API</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
Log out <DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>gap-2 and size unclassed icons to size-4 automatically. Icons inherit the item color, including the accent foreground while the item is focused.<DropdownMenuContent className="w-56">
<DropdownMenuGroup>
<DropdownMenuItem>
<UserIcon /> Profile
</DropdownMenuItem>
<DropdownMenuItem>
<CreditCardIcon /> Billing
</DropdownMenuItem>
<DropdownMenuItem>
<GearSixIcon /> Settings
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<SignOutIcon /> Log out
</DropdownMenuItem>
</DropdownMenuContent>checked and onCheckedChange, and the check indicator renders at the end of the row. The menu closes on select by default — call event.preventDefault() in onSelect to keep it open while toggling several options.const [showStatusBar, setShowStatusBar] = React.useState(true)
const [showActivityBar, setShowActivityBar] = React.useState(false)
const [showPanel, setShowPanel] = React.useState(false)
<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>value and reports changes through onValueChange; each DropdownMenuRadioItem marks the active choice with the same end-of-row indicator.const [position, setPosition] = React.useState("bottom")
<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><DropdownMenuContent className="w-56">
<DropdownMenuItem>
<UsersIcon /> Team
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<UserPlusIcon /> Invite users
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuItem>
<EnvelopeSimpleIcon /> Email
</DropdownMenuItem>
<DropdownMenuItem>
<ChatCircleIcon /> Message
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<PlusCircleIcon /> More…
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuSub>
<DropdownMenuSeparator />
<DropdownMenuItem>
New Team <DropdownMenuShortcut>⌘T</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuContent>inset to the plain ones — it pads the start by the icon column (ps-8) so every label lines up. The prop exists on items, labels and sub triggers alike.<DropdownMenuContent className="w-56">
<DropdownMenuLabel inset>Actions</DropdownMenuLabel>
<DropdownMenuItem>
<UserPlusIcon /> Assign member
</DropdownMenuItem>
<DropdownMenuItem inset>Rename</DropdownMenuItem>
<DropdownMenuItem inset>Duplicate</DropdownMenuItem>
</DropdownMenuContent>variant="destructive" paints the item — icon included — with the destructive token and swaps the focus highlight for a translucent destructive wash. Reserve it for the irreversible row and keep a separator before it.<DropdownMenuContent className="w-56">
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Duplicate</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
<TrashIcon /> Delete
</DropdownMenuItem>
</DropdownMenuContent>asChild — here an icon-sized ghost button wrapping an Avatar, the classic account switcher. Align the panel to the end so it hangs off the avatar edge, and give the hidden label an sr-only span.<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="rounded-full">
<Avatar className="size-8">
<AvatarImage src="https://github.com/shadcn.png" alt="@westy" />
<AvatarFallback>WD</AvatarFallback>
</Avatar>
<span className="sr-only">Open account menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="end">
<DropdownMenuLabel>
<div className="flex flex-col gap-0.5">
<span className="text-foreground">Westy</span>
<span className="font-normal">hello@westy.la</span>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>Profile</DropdownMenuItem>
<DropdownMenuItem>Billing</DropdownMenuItem>
<DropdownMenuItem>Notifications</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>Log out</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>dir="rtl" on the DropdownMenu root. Items, indicators and shortcuts are laid out with logical properties (ps-8, pe-8, end-2, ms-auto), and the submenu caret flips via rtl:rotate-180 — so the whole menu mirrors without extra classes.<DropdownMenu dir="rtl">
<DropdownMenuTrigger asChild>
<Button variant="outline">فتح القائمة</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuLabel>حسابي</DropdownMenuLabel>
<DropdownMenuGroup>
<DropdownMenuItem>
الملف الشخصي <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
الفواتير <DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>تسجيل الخروج</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>aria-haspopup and aria-expanded, arrow keys and typeahead move focus between items, Enter or Space activates, and Escape closes and returns focus to the trigger. Checkbox and radio items announce their state via aria-checked; disabled items are skipped. DropdownMenuShortcut is a purely visual hint — it is not announced and does not register a key handler. Icon-only triggers still need an accessible name.<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<DotsThreeVerticalIcon />
<span className="sr-only">Open menu</span>
</Button>
</DropdownMenuTrigger>asChild to keep your own markup), and DropdownMenuContent handles the portal and positioning automatically — DropdownMenuPortal exists for advanced composition only. Every Radix prop passes through; see the Radix documentation for the full primitive API.| Component | Prop | Default |
|---|---|---|
| DropdownMenu | open?: boolean, onOpenChange?: (open: boolean) => void | — |
| DropdownMenu | modal?: boolean | true |
| DropdownMenu | dir?: "ltr" | "rtl" | "ltr" |
| DropdownMenuTrigger | asChild?: boolean | false |
| DropdownMenuContent | side?: "top" | "right" | "bottom" | "left" | "bottom" |
| DropdownMenuContent | align?: "start" | "center" | "end" | "start" |
| DropdownMenuContent | sideOffset?: number | 4 |
| DropdownMenuItem | variant?: "default" | "destructive" | "default" |
| DropdownMenuItem | inset?: boolean, disabled?: boolean, onSelect?: (event: Event) => void | — |
| DropdownMenuCheckboxItem | checked?: boolean, onCheckedChange?: (checked: boolean) => void | — |
| DropdownMenuRadioGroup | value?: string, onValueChange?: (value: string) => void | — |
<DropdownMenu data-slot="dropdown-menu" /> <DropdownMenuTrigger data-slot="dropdown-menu-trigger" /> <DropdownMenuContent data-slot="dropdown-menu-content" data-side data-align /> <DropdownMenuGroup data-slot="dropdown-menu-group" /> <DropdownMenuLabel data-slot="dropdown-menu-label" data-inset /> <DropdownMenuItem data-slot="dropdown-menu-item" data-variant data-inset /> <DropdownMenuCheckboxItem data-slot="dropdown-menu-checkbox-item" data-state /> <DropdownMenuRadioGroup data-slot="dropdown-menu-radio-group" /> <DropdownMenuRadioItem data-slot="dropdown-menu-radio-item" data-state /> <DropdownMenuSeparator data-slot="dropdown-menu-separator" /> <DropdownMenuShortcut data-slot="dropdown-menu-shortcut" /> <DropdownMenuSub data-slot="dropdown-menu-sub" /> <DropdownMenuSubTrigger data-slot="dropdown-menu-sub-trigger" data-open /> <DropdownMenuSubContent data-slot="dropdown-menu-sub-content" />
Tokens used
13 design tokens consumed by the Dropdown Menu component.
--popover
Popover
--popover-foreground
Popover Foreground
--accent
Accent
--accent-foreground
Accent Foreground
--destructive
Destructive
--muted-foreground
Muted Foreground
--foreground
Foreground
--border
Border
--radius-md
Radius 0.8× (8px)
--radius-sm
Radius 0.6× (6px)
--text-sm
Text Small (14px)
--text-xs
Text Extra Small (12px)
--spacing
Spacing unit (4px)