Dropdown Menu
A contextual menu that opens from a trigger, built on Radix UI. Supports grouped items, leading/trailing icons, keyboard shortcuts, descriptions, submenus, checkboxes, radio groups, and full RTL layout.
Playground
Installation
pnpm add @tessinaui/uiUsage
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
} from "@tessinaui/ui";
import { Button } from "@tessinaui/ui";<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">Open</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>Profile</DropdownMenuItem>
<DropdownMenuItem>Settings</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem intent="error">Sign out</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>Examples
Default
A basic menu with a label, items, and a destructive sign-out action.
Sizes
Five sizes from xs to xl, each scaling padding, text, and icons.
With Icons & Shortcuts
Items with leading icons and keyboard shortcut badges.
Checkbox Items
Toggleable checkbox items that hold their own checked state.
Radio Group
Mutually exclusive options grouped in a radio group.
Submenu
Nested submenus for grouping related actions behind a trigger.
API Reference
DropdownMenuContent
The panel that contains menu items. Pass size and width here — they propagate automatically to all child items via React context.
| Prop | Type | Default | Description |
|---|---|---|---|
size | "xs" | "sm" | "md" | "lg" | "xl" | "md" | Controls panel padding, item padding, text size, and icon size |
width | "narrow" | "default" | "wide" | "default" | Controls minimum width of the panel |
side | "top" | "right" | "bottom" | "left" | "bottom" | Preferred side to open on (Radix) |
align | "start" | "center" | "end" | "start" | Alignment relative to the trigger (Radix) |
sideOffset | number | 4 | Gap in px between trigger and panel (Radix) |
dir | "ltr" | "rtl" | — | Text direction — flips icon order and submenu side |
All other Radix Content props are forwarded.
DropdownMenuItem
| Prop | Type | Default | Description |
|---|---|---|---|
leadingIcon | React.ReactNode | — | Icon on the start side |
trailingIcon | React.ReactNode | — | Icon on the end side |
shortcut | string | — | Keyboard shortcut badge (e.g. "⌘K") |
description | string | — | Secondary description line below the label |
intent | "none" | "error" | "none" | "error" applies destructive (red) styling to text and icons |
inset | boolean | false | Adds start padding for alignment when some items have icons and others don't |
disabled | boolean | false | Disables the item and mutes its appearance |
All native Radix Item props are forwarded.
DropdownMenuLabel
Section heading inside the menu. Automatically sized to match the content size.
| Prop | Type | Default | Description |
|---|---|---|---|
inset | boolean | false | Adds start padding to align with inset items |
DropdownMenuSeparator
Horizontal divider between groups. No props needed.
DropdownMenuShortcut
Standalone keyboard badge. Used internally by DropdownMenuItem but can be composed manually.
<DropdownMenuShortcut>⌘K</DropdownMenuShortcut>DropdownMenuSub / SubTrigger / SubContent
Three-part pattern for nested submenus:
<DropdownMenuSub>
<DropdownMenuSubTrigger leadingIcon={<Share />}>Share</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuItem>Email</DropdownMenuItem>
<DropdownMenuItem>Copy link</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuSub>DropdownMenuSubTrigger accepts leadingIcon and inset. DropdownMenuSubContent inherits size from parent context.
DropdownMenuCheckboxItem
Checkbox-style item that maintains checked state:
const [checked, setChecked] = useState(false);
<DropdownMenuCheckboxItem checked={checked} onCheckedChange={setChecked}>
Show toolbar
</DropdownMenuCheckboxItem>DropdownMenuRadioGroup / RadioItem
Radio-group for mutually exclusive selection:
const [theme, setTheme] = useState("system");
<DropdownMenuRadioGroup value={theme} onValueChange={setTheme}>
<DropdownMenuRadioItem value="light">Light</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="dark">Dark</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="system">System</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>Sizes
All five sizes scale item padding, text size, icon size, and panel padding proportionally. Pass size once to DropdownMenuContent:
<DropdownMenuContent size="sm">
<DropdownMenuItem>Profile</DropdownMenuItem>
</DropdownMenuContent>Width Variants
{/* Compact narrow panel */}
<DropdownMenuContent width="narrow">...</DropdownMenuContent>
{/* Default width (min-w-[12rem]) */}
<DropdownMenuContent width="default">...</DropdownMenuContent>
{/* Wide panel — useful for items with descriptions */}
<DropdownMenuContent width="wide">...</DropdownMenuContent>With Icons, Shortcut & Description
<DropdownMenuItem
leadingIcon={<Settings />}
shortcut="⌘,"
description="Manage your preferences"
>
Settings
</DropdownMenuItem>Destructive Items
<DropdownMenuItem intent="error" leadingIcon={<Trash />} shortcut="⌫">
Delete
</DropdownMenuItem>RTL
Pass dir="rtl" to DropdownMenuContent. The layout, icon positions, and submenu open direction all flip automatically:
<DropdownMenuContent dir="rtl">
<DropdownMenuItem leadingIcon={<User />}>الملف الشخصي</DropdownMenuItem>
</DropdownMenuContent>Accessibility
- Built on Radix UI
@radix-ui/react-dropdown-menu— full keyboard navigation out of the box - Arrow keys move focus between items;
Enter/Spaceactivates;Escapecloses Tabdismisses the menu and returns focus to the trigger- Each item is a focusable
[role="menuitem"] - Disabled items have
[data-disabled]andpointer-events-none - Color contrast: all intent states meet WCAG 2.1 AA
- Submenus open on
ArrowRight(LTR) /ArrowLeft(RTL)
Drawer
An accessible slide-in panel built on Base UI Dialog. Compound component pattern with Header, Body, Footer, Title, and Description. Four sides (right, left, top, bottom). Five sizes. Five corner-rounding options. Auto-stacking footer for sheets. LTR and RTL support.
EmptyState
Centered surface for "no data here" — empty lists, search results, tables, dashboards, inboxes, 404s, first-run / onboarding moments. Three variants, four sizes, six intents tinting the icon tile, primary + secondary actions, optional footer, LTR/RTL.