Import
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuRadioGroup,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
} from "@timelycare/helix-ui"
Props
interface DropdownMenuItemProps {
disabled?: boolean
onSelect?: () => void
className?: string
children: React.ReactNode
}
interface DropdownMenuCheckboxItemProps extends DropdownMenuItemProps {
checked?: boolean
onCheckedChange?: (checked: boolean) => void
}
Design Tokens
Container
| Property | Token | Value |
|---|---|---|
| Background | bg-popover | white |
| Border | border | 1px solid border color |
| Border radius | rounded-md | 8px |
| Padding | p-1 | 4px |
| Shadow | shadow-md | Multi-layer drop shadow |
| Min width | — | 224px |
Item Sizing
| Property | Token | Value |
|---|---|---|
| Height | — | 32px |
| Padding (default) | px-2 py-1.5 | 8px horizontal, 6px vertical |
| Padding (with indicator) | pl-8 pr-2 py-1.5 | 32px left, 8px right, 6px vertical |
| Border radius | rounded-sm | 6px |
| Gap | gap-2 | 8px |
Typography
| Element | Size | Color | Weight |
|---|---|---|---|
| Item text | text-sm (14px) | text-popover-foreground | normal |
| Shortcut text | text-xs (12px) | opacity-60 | normal |
| Label | text-sm (14px) | text-popover-foreground | semibold |
Icons & Indicators
| Property | Token | Value |
|---|---|---|
| Icon size | size-4 | 16px × 16px |
| Indicator position | left-2 | 8px from left edge |
| Icon color (default) | text-muted-foreground | #646565 |
| Check indicator color | text-foreground | #09090b |
| SubTrigger chevron | size-4 | ChevronRight icon |
Separator
| Property | Token | Value |
|---|---|---|
| Height | h-px | 1px |
| Color | border | #e4e4e7 |
Item Variants
| Variant | Use For | Tailwind |
|---|---|---|
default | Standard actions | text-popover-foreground |
checkbox | Toggleable settings | text-popover-foreground + checkmark indicator |
radio | Single-select options | text-popover-foreground + dot indicator |
icon | Actions with leading icons | Icon (text-muted-foreground) + text |
Choosing a Variant
- Use default for actions (Profile, Logout, Delete)
- Use checkbox for independent toggles (Show Status Bar)
- Use radio for mutually exclusive choices (Panel Position)
- Use icon for items with contextual icons
States
| State | Implementation |
|---|---|
| Default | text-popover-foreground |
| Hover | bg-accent text-accent-foreground (automatic) |
| Focus | bg-accent text-accent-foreground |
| Disabled | opacity-50 pointer-events-none |
| Error | text-destructive (icon variant only) |
Common Patterns
Basic Menu
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">Open</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>
Profile <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>Billing</DropdownMenuItem>
<DropdownMenuItem>Settings</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>Logout</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
With Submenu
<DropdownMenuSub>
<DropdownMenuSubTrigger>Invite users</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuItem>Email</DropdownMenuItem>
<DropdownMenuItem>Message</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuSub>
Checkbox & Radio Items
<DropdownMenuCheckboxItem checked={showPanel} onCheckedChange={setShowPanel}>
Status Bar
</DropdownMenuCheckboxItem>
<DropdownMenuRadioGroup value={position} onValueChange={setPosition}>
<DropdownMenuLabel>Panel Position</DropdownMenuLabel>
<DropdownMenuRadioItem value="top">Top</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="bottom">Bottom</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
Action Menu with Icons
Row-level action menus with icon indicators and destructive actions.
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<MoreHorizontal className="size-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>
<Pencil className="size-4 mr-2" />
Edit
</DropdownMenuItem>
<DropdownMenuItem>
<Copy className="size-4 mr-2" />
Duplicate
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem className="text-destructive">
<Trash2 className="size-4 mr-2" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
Icon Spacing
- Icon to text:
mr-2(8px) - Icon size:
size-4(16px) - Destructive items:
text-destructiveon the item
Avatar Trigger
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="relative h-10 w-10 rounded-full">
<Avatar><AvatarImage src="/user.jpg" /></Avatar>
</Button>
</DropdownMenuTrigger>
Accessibility
- Built on Radix DropdownMenu — handles keyboard and focus automatically
- Keyboard: Enter/Space to open, Arrow keys to navigate, Enter to select, Escape to close
- Screen reader: announces menu role, item count, and selection
- Typeahead search supported within menu items
Gotchas
| Problem | Solution |
|---|---|
| Menu not opening | Add asChild to DropdownMenuTrigger |
| Shortcut not aligned | Use DropdownMenuShortcut (has ml-auto) |
| Submenu not showing | Wrap in DropdownMenuSub with SubTrigger + SubContent |
| Radio items not grouped | Wrap in DropdownMenuRadioGroup |
See Also
- Related Components: Context Menu (right-click menu), Menubar (persistent menu bar)
- Accessibility: Navigation Accessibility — Menu ARIA patterns
- Patterns: Component Match — Menu component selection
Last updated: February 9, 2026