Dropdown Menu
Context menu component built on Radix DropdownMenu. Supports groups, submenus, checkbox/radio items, and keyboard shortcuts.
Compact actions in contextLink to section
The Dropdown Menu component displays a list of actions inside a floating surface anchored to a trigger.
Use it for:
- row actions
- account menus
- editor menus
- compact command lists
- secondary actions
- option selectors
- small contextual choices
Dropdown Menu is built on Radix DropdownMenu and styled with PyColors tokens such as bg-popover, border-border, and text-popover-foreground.
Core idea
Dropdown Menu is for compact contextual actions. It should not become a full workflow surface.
ImportLink to section
import { DropdownMenu } from "@pycolors/ui";Basic usageLink to section
Use DropdownMenuTrigger asChild with a semantic trigger, usually a Button.
import {
Button,
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
} from "@pycolors/ui";
export function BasicDropdownMenu() {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm">
Open menu
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>Profile</DropdownMenuItem>
<DropdownMenuItem>Settings</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem className="text-destructive">
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}Trigger rule
Prefer asChild so the trigger keeps the semantics of the element you pass, such as a real button.
When to use Dropdown MenuLink to section
Use Dropdown Menu when the actions are secondary, contextual, or compact.
Row actions
Use for rename, duplicate, archive, export, or delete actions on one record.
Preferences
Use checkbox or radio items for compact option selection.
Account menu
Use for compact user-level navigation such as profile, settings, or sign out.
Grouping, labels, and shortcutsLink to section
Use labels and groups when a menu has more than one kind of action.
import {
Button,
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
} from "@pycolors/ui";
export function GroupedDropdownMenu() {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm">
Actions
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuLabel>Project</DropdownMenuLabel>
<DropdownMenuGroup>
<DropdownMenuItem>
Rename
<DropdownMenuShortcut>⌘R</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
Duplicate
<DropdownMenuShortcut>⌘D</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem className="text-destructive">
Delete
<DropdownMenuShortcut>⌫</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}Shortcut rule
DropdownMenuShortcut is visual. It does not automatically register keyboard shortcuts.
Checkbox itemsLink to section
Use checkbox items for independent toggles.
Interactive examples with state must live in a Client Component.
"use client";
import * as React from "react";
import {
Button,
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuCheckboxItem,
DropdownMenuSeparator,
} from "@pycolors/ui";
export function CheckboxMenuPreview() {
const [enabled, setEnabled] = React.useState(true);
const [pinned, setPinned] = React.useState(false);
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm">
Options
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuCheckboxItem
checked={enabled}
onCheckedChange={(value) => setEnabled(Boolean(value))}
>
Enabled
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem
checked={pinned}
onCheckedChange={(value) => setPinned(Boolean(value))}
>
Pinned
</DropdownMenuCheckboxItem>
<DropdownMenuSeparator />
<DropdownMenuCheckboxItem checked disabled>
Disabled option
</DropdownMenuCheckboxItem>
</DropdownMenuContent>
</DropdownMenu>
);
}Radio groupLink to section
Use radio items for exclusive selection.
"use client";
import * as React from "react";
import {
Button,
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
} from "@pycolors/ui";
export function RadioMenuPreview() {
const [theme, setTheme] = React.useState("system");
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm">
Theme
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuLabel>Appearance</DropdownMenuLabel>
<DropdownMenuRadioGroup value={theme} onValueChange={setTheme}>
<DropdownMenuRadioItem value="system">System</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="light">Light</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="dark">Dark</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
<DropdownMenuSeparator />
<DropdownMenuRadioItem value="contrast" disabled>
High contrast (soon)
</DropdownMenuRadioItem>
</DropdownMenuContent>
</DropdownMenu>
);
}SubmenusLink to section
Use submenus sparingly.
They are useful when a single action has a small secondary choice, such as export format.
import {
Button,
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
DropdownMenuSeparator,
} from "@pycolors/ui";
export function DropdownSubmenu() {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm">
More
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>Rename</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
Export
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuItem>PDF</DropdownMenuItem>
<DropdownMenuItem>CSV</DropdownMenuItem>
<DropdownMenuItem>JSON</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuSub>
<DropdownMenuSeparator />
<DropdownMenuItem className="text-destructive">
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}Submenu rule
Keep submenu depth to one level whenever possible. Deep nested menus are harder to scan and navigate.
Usage patternsLink to section
Start with the triggerLink to section
Use a clear trigger such as Actions, More, or an icon button with an accessible label.
Keep items action-orientedLink to section
Prefer verb-first labels: Rename, Duplicate, Archive, Export, Delete.
Group only when neededLink to section
Use labels and separators to clarify structure, not to decorate the menu.
Confirm destructive actionsLink to section
A destructive menu item should usually open a Dialog before the final action.
Move complex workflows elsewhereLink to section
If the menu contains forms, long content, or multi-step decisions, use Sheet, Dialog, or a page.
Dropdown Menu vs Dialog vs SheetLink to section
| Situation | Use |
|---|---|
| Row actions | DropdownMenu |
| Account menu | DropdownMenu |
| Small preference selector | DropdownMenu |
| Destructive confirmation | Dialog |
| Advanced filters | Sheet |
| Long settings workflow | Sheet or page |
| Multi-step flow | Page |
Decision rule
DropdownMenu exposes compact choices. Dialog confirms focused decisions. Sheet handles larger side workflows.
APILink to section
ComponentsLink to section
| Component | Description |
|---|---|
DropdownMenu | Root container |
DropdownMenuTrigger | Anchor element, usually with asChild |
DropdownMenuContent | Floating menu surface |
DropdownMenuItem | Standard action item |
DropdownMenuCheckboxItem | Independent toggle item |
DropdownMenuRadioGroup | Exclusive selection group |
DropdownMenuRadioItem | Item inside a radio group |
DropdownMenuLabel | Section label |
DropdownMenuSeparator | Divider |
DropdownMenuShortcut | Right-aligned visual shortcut |
DropdownMenuGroup | Group wrapper |
DropdownMenuSub | Submenu root |
DropdownMenuSubTrigger | Submenu trigger item |
DropdownMenuSubContent | Submenu surface |
DropdownMenuPortal | Portal wrapper for menu content |
Wrapper additionsLink to section
| Component | Added prop | Purpose |
|---|---|---|
DropdownMenuItem | inset?: boolean | Align item with indicator spacing |
DropdownMenuLabel | inset?: boolean | Align label with inset items |
DropdownMenuSubTrigger | inset?: boolean | Align submenu trigger with inset items |
Dropdown Menu components extend their respective Radix props.
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Root>
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Trigger>
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item>AccessibilityLink to section
Dropdown Menu is built on Radix DropdownMenu primitives.
Keyboard support includes:
- Enter or Space activates an item
- Arrow keys navigate items
- Escape closes the menu
- Submenus support keyboard navigation
- Focus returns to the trigger when closed
Guidelines:
- use
DropdownMenuTrigger asChildwith a semantic trigger - provide an
aria-labelfor icon-only triggers - avoid complex inputs inside dropdown menus
- do not rely on shortcut text as the only instruction
- confirm destructive actions in a Dialog when needed
Accessibility rule
A dropdown menu should be quick to scan and quick to leave. If users need to think, read, or type, use another surface.
Prefer / avoidLink to section
Prefer
- short action labels
- semantic triggers
- groups only when they improve scanning
- Dialog confirmation for destructive actions
- submenus only for small secondary choices
Avoid
- long paragraphs inside menus
- deep nested submenus
- forms or complex inputs in a dropdown
- destructive actions with no confirmation
- using menus for primary actions
Product copy guidelinesLink to section
Menu item labels should be short and action-oriented.
- Rename
- Duplicate
- Archive
- Export CSV
- Delete
- Click here to rename this project
- Make another copy of this project
- Remove forever immediately
- Open another complicated flow