Components · Actions
Dropdown menu
BetaA menu of actions on one item, such as a client row.
- Source
components/ui/dropdown-menu.tsx - shadcn/ui Dropdown menu
- Figma: not linked yet (Q13)
Sara Al-Harbi
Fat loss · week 4 of 12
When to use
- For the secondary actions on one item, behind a "More" button: a client row, a program card, an invoice.
- For view options that don't need to be on screen all the time: which columns to show, how to sort.
- For the extra options of a split button (see Button group).
When not to use
- To pick a value for a form field: use a Select. A menu runs actions; a select stores a value.
- For the one main action on a page: show it as a Button, not hidden in a menu.
- For two or three actions with room to show them: use buttons. Hidden actions are found less often.
- For navigation between pages: use links, or the Sidebar.
Anatomy
- 1. Trigger: a Button, often icon-only with an aria-label
- 2. Label (optional): names the menu or the item it acts on
- 3. Item: optional 16px icon, then a verb
- 4. Submenu trigger: a chevron at the end opens a nested menu
- 5. Destructive item: last, after a separator
Open the menu in the example to see parts 2 to 5. Separators split groups of related items.
Variants
| Item type | Use for |
|---|---|
DropdownMenuItem | An action. Closes the menu when chosen. |
DropdownMenuItem variant="destructive" | An action that removes data. Put it last, and confirm it in an Alert dialog. |
DropdownMenuCheckboxItem | Options that are each on or off: columns in the client list. |
DropdownMenuRadioGroup + RadioItem | One option from a set: sort order. |
DropdownMenuSub | A short second level, such as the programs you can assign. One level only. |
DropdownMenuLabel, Separator, Shortcut | Structure. Show a shortcut only if it really works in that view. |
Use inset on items without icons in a menu where other items have icons, so the text lines up.
States
| State | What changes | Token |
|---|---|---|
| Open | Menu panel with a border and medium shadow, on the dropdown layer | bg-popover, shadow-md, z-(--z-dropdown) |
| Highlighted item (hover or arrow keys) | Accent fill and text | bg-accent, text-accent-foreground |
| Open submenu trigger | Stays highlighted while its submenu is open | data-[state=open]:bg-accent |
| Checked | Check mark (checkbox) or dot (radio) at the start | ItemIndicator |
| Disabled item | Half opacity, skipped by arrow keys | opacity-(--opacity-disabled) |
| Destructive item | Red text and icon; a red tint when highlighted | text-destructive-text, bg-destructive/10 |
| Trigger focus-visible | The Button's ring | ring-ring/50 |
The disabled state is in the "Sort" menu above: "Reset to default order" is disabled until you change the order.
Destructive items use text-destructive-text, so they stay above 4.5:1 when highlighted on their 10% destructive tint. Upstream's text-destructive computed to about 4.1:1 there.
Usage guidelines
- Order items by frequency, then group them: view, edit, share; then status changes; then destructive.
- Keep menus short: about 7 items. More than that usually means some belong on the page.
- Don't put a form or free text inside a menu: use a Popover or Dialog.
- In a table, every row's trigger has the same position and icon, and its
aria-labelincludes the row's name.
Content guidelines
- Items start with a verb and name the object when it isn't obvious: "Send message", "Assign program", "Remove client".
- Sentence case, no ending punctuation. Add "…" when the item opens a dialog that asks for more ("Remove client…").
- Checkbox and radio items are nouns, not verbs: "Last check-in", "Next payment".
- Arabic items use the verbal noun consistently: "إرسال رسالة"، "تعيين برنامج"، "إزالة العميل…".
Accessibility
| Key | Does |
|---|---|
| Enter, Space or Arrow down | On the trigger: opens the menu and focuses the first item |
| Arrow up / down | Moves between items, skipping disabled ones; wraps only if loop is set |
| Arrow right (left in RTL), Enter or Space | On a submenu trigger: opens the submenu |
| Arrow left (right in RTL), Escape in a submenu | Closes the submenu |
| Enter or Space | Chooses the item. Checkbox and radio items toggle |
| Type a letter | Moves to the next item starting with it |
| Escape | Closes the menu and returns focus to the trigger |
| Tab | Does nothing: focus stays in the menu until an item is chosen or Escape is pressed |
- The trigger has
aria-haspopup="menu"andaria-expanded. The panel is amenu; items aremenuitem,menuitemcheckboxormenuitemradio. - A screen reader announces the trigger as "Actions for Sara Al-Harbi, menu button, collapsed", then each item as "Send message, menu item, 2 of 5" (exact words vary by screen reader).
- Focus moves into the menu on open and back to the trigger on close. Nothing outside the menu can be clicked or focused while it is open (it is modal by default).
- Destructive items need a confirmation; color alone doesn't say "this deletes".
RTL
- The menu aligns to the trigger's start or end edge as set by
align, soalign="end"opens toward the left in RTL. - Icons and check marks sit at the start of each item and move to the right. Shortcuts sit at the end.
- The submenu chevron mirrors (
rtl:-scale-x-100) and submenus open toward the left. Arrow keys follow the direction because the site provides Radix'sDirectionProvider.
Tokens used
Read from components/ui/dropdown-menu.tsx at build time, so this list changes when the component does. Opacity modifiers such as /90 are shadcn hover and focus tints on top of the token.
| Token | Utilities | SuperCoach | Coach |
|---|---|---|---|
| --accent | bg-accent | brand-50 | orange-50 |
| --accent-foreground | text-accent-foreground | brand-700 | orange-700 |
| --border | bg-border | neutral-200 | neutral-200 |
| --destructive | bg-destructive/10 text-destructive | red-600 | red-600 |
| --destructive-text | text-destructive-text | red-700 | red-700 |
| --muted-foreground | text-muted-foreground | neutral-600 | neutral-600 |
| --popover | bg-popover | white | white |
| --popover-foreground | text-popover-foreground | neutral-950 | neutral-950 |
- Layer
z-(--z-dropdown) - Opacity
opacity-(--opacity-disabled) - Radius
rounded-md - Radius
rounded-sm - Shadow
shadow-lg - Shadow
shadow-md - Type
font-medium - Type
text-sm - Type
tracking-wide - Type
text-xs
Props
<DropdownMenu>
Also takes every prop of Radix DropdownMenu.Root
No props of its own.
<DropdownMenuTrigger>
Also takes every prop of Radix DropdownMenu.Trigger
No props of its own.
<DropdownMenuContent>
Also takes every prop of Radix DropdownMenu.Content
No props of its own.
<DropdownMenuItem>
Also takes every prop of Radix DropdownMenu.Item
| Prop | Type | Default |
|---|---|---|
| inset? | boolean | — |
| variant? | "default" | "destructive" | "default" |
<DropdownMenuCheckboxItem>
Also takes every prop of Radix DropdownMenu.CheckboxItem
No props of its own.
<DropdownMenuRadioItem>
Also takes every prop of Radix DropdownMenu.RadioItem
No props of its own.
<DropdownMenuLabel>
Also takes every prop of Radix DropdownMenu.Label
| Prop | Type | Default |
|---|---|---|
| inset? | boolean | — |
<DropdownMenuSubTrigger>
Also takes every prop of Radix DropdownMenu.SubTrigger
| Prop | Type | Default |
|---|---|---|
| inset? | boolean | — |
Replaces in coach-admin
From the read-only audit of coach-admin: 94 style values, 178 uses.
coach-admin has at least five action menus: Bootstrap's .dropdown-menu, the shared ActionDropdown, .actionMenu, the landing page's SectionActionsDropdown and the bulk-actions menu. All become this component. Dropdowns that pick a value (dropdown-list, el-select) map to Select instead.
Classes and selectors
.dropdown-menu67.dropdown-item:hover:first-child12.dropdown-menu:not(.nutrition-tab__dropdown-menu)12.actionMenuButton10.dropdown-item:hover9.dropdown-item9.section-actions-dropdown__toggle6.filter-dropdown-menu5.section-actions-dropdown__menu5.action-dropdown-menu5.dropdown-item:hover:last-child4.actionMenu-content4
Files
src/views/LandingPage/components/SectionActionsDropdown/SectionActionsDropdown.scss20src/reusableComponents/formComponents/action-dropdown/ActionDropdown.scss11
The inventory lists the first 12 locations of each value, so file counts are a lower bound.
SCSS variables in these styles
$border-color→--border$white-text-color→--primary-foreground$spacing-2xsnot a color: spacing, size or layout$secondary-color→ nearest orange-500 (no semantic token)$line-height-basenot a color: spacing, size or layout$secondary-color-hover→ nearest red-200 (no semantic token)$spacing-xsnot a color: spacing, size or layout$spacing-smnot a color: spacing, size or layout$dropdown-border-color→ nearest neutral-300 (no semantic token)$icon-color-hover-filternot a color: spacing, size or layout