Menu
A list of actions on a temporary surface. Icons and trailing text, checkbox and radio items, groups and submenus, with arrow-key navigation.
Version 0.4.1 ·
Installation
bunx --bun shadcn@latest add @dga/menuUsage
import { Edit02Icon, Delete02Icon } from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";
import { Button } from "@/components/ui/button";
import {
Menu,
MenuContent,
MenuItem,
MenuSeparator,
MenuTrigger,
} from "@/components/ui/menu";
export function RequestActions() {
return (
<Menu>
<MenuTrigger render={<Button variant="outline" />}>Actions</MenuTrigger>
<MenuContent>
<MenuItem>
<HugeiconsIcon icon={Edit02Icon} aria-hidden />
Edit request
</MenuItem>
<MenuSeparator />
<MenuItem>
<HugeiconsIcon icon={Delete02Icon} aria-hidden />
Withdraw request
</MenuItem>
</MenuContent>
</Menu>
);
}Use a Menu for a set of actions on one thing: a table row, a card, a profile. For choosing a value that is saved in a form, use Select. Items run an action when they are chosen. Put an icon first (it is decorative), the label, then optional trailing content such as MenuShortcut. For a link, render an anchor: <MenuItem render={<a href="/" />}>. Keep the menu to about seven items; split a longer one with MenuSeparator and MenuGroup.
Anatomy
- Trigger: the button that opens the menu.
- Menu (
role="menu"):background-menu, a 1px border, radius-md,shadow-2xl. - Item (
role="menuitem"): 40px high, 8px padding, an optional icon and trailing content. - Checkbox and radio items: show a check mark or a dot on the end side when chosen.
- Group and label, separator: structure for longer menus.
- Submenu: MenuSub with a MenuSubTrigger (chevron) and MenuSubContent.
Examples
Checkbox and radio items
MenuCheckboxItem turns something on or off. MenuRadioGroup with MenuRadioItem chooses one. Groups get a MenuLabel.
Submenu
MenuSub opens a second list beside its item, on the end side. It opens with Enter, Space or the arrow that points at it, and closes with the opposite arrow.
Stay open and controlled
A checkbox or radio item can set closeOnClick={false} so the menu stays open while the person changes several options. Pass open and onOpenChange to Menu to read or set its state. An ordinary item still closes the menu.
RTL
Menus align to the start edge of the trigger (the right edge in Arabic). A submenu opens on the end side, so on the left, and its chevron is mirrored. Left and Right swap: Left opens a submenu in Arabic. Digits and shortcuts stay left to right.
Accessibility
| Key | Action |
|---|---|
| Enter | On the trigger, opens the menu and focuses the first item. On an item, chooses it. |
| Space | Chooses the focused item (or opens the menu from the trigger). |
| ArrowDown | Opens the menu from the trigger; moves to the next item. |
| ArrowUp | Moves to the previous item. |
| Home | Moves to the first item (and End to the last). |
| ArrowRight | Opens a submenu (ArrowLeft in Arabic); the opposite arrow closes it. |
| A to Z | Moves to the next item that starts with that letter. |
| Escape | Closes the menu and returns focus to the trigger. |
- Built on Base UI Menu:
role="menu", items withrole="menuitem",menuitemcheckboxandmenuitemradio, and the trigger hasaria-haspopupandaria-expanded. - Only one item is in the tab order at a time (roving focus); the arrow keys move between items and focus returns to the trigger on close.
- Hover and keyboard highlight show a fill; keyboard focus adds a 2px outline inside the item (3:1 or better). A selected item has a tint, a check mark or a dot, so it never relies on colour alone.
- Text is 4.5:1 and icons 3:1 or better in every state, in light and dark. A disabled item stays in the arrow-key order so it can be found, but cannot be chosen.
API Reference
Menu
A list of actions on a temporary surface (DGA: Menu). Arrow keys move through the items, Home and End jump to the first and last, typing a letter jumps to a match, Escape closes it and focus returns to the trigger. Submenus open on the end side and mirror on Arabic pages. A disabled item can still be reached with the arrow keys, but it cannot be chosen.
| Prop | Type | Default |
|---|---|---|
booleanWhether the menu is currently open. | No default | |
| ||
booleanWhether the menu is initially open. To render a controlled menu, use the `open` prop instead. | No default | |
| ||
(open: boolean) => voidEvent handler called when the menu is opened or closed. | No default | |
| ||
Also accepts every prop of the Base UI Menu (opens in a new tab).
MenuCheckboxItem
An item that can be turned on and off. It shows a check mark while it is on.
| Prop | Type | Default |
|---|---|---|
booleanWhether the checkbox item is currently ticked. To render an uncontrolled checkbox item, use the `defaultChecked` prop instead. | No default | |
| ||
(checked: boolean) => voidEvent handler called when the checkbox item is ticked or unticked. | No default | |
| ||
Also accepts every prop of the Base UI Menu (opens in a new tab).
MenuContent
The popup list. It opens below the trigger, aligned to its start side.
Also accepts every prop of the Base UI Menu (opens in a new tab).
MenuGroup
Groups related items under a MenuLabel.
Also accepts every prop of the Base UI Menu (opens in a new tab).
MenuItem
One action. Put an icon first, the label, then an optional MenuShortcut, Tag or Switch. Use `render` to make it a link: `<MenuItem render={<a href="/" />}>`.
| Prop | Type | Default |
|---|---|---|
booleanWhether the component should ignore user interaction. | No default | |
| ||
booleanWhether to close the menu when the item is clicked. | No default | |
| ||
(event) => voidThe click handler for the menu item. | No default | |
| ||
Also accepts every prop of the Base UI Menu (opens in a new tab).
MenuLabel
The heading of a MenuGroup.
Also accepts every prop of the Base UI Menu (opens in a new tab).
MenuRadioGroup
Groups MenuRadioItem options so only one is chosen.
| Prop | Type | Default |
|---|---|---|
(value: string) => voidFunction called when the selected value changes. | No default | |
| ||
stringThe controlled value of the radio item that should be currently selected. To render an uncontrolled radio group, use the `defaultValue` prop instead. | No default | |
| ||
Also accepts every prop of the Base UI Menu (opens in a new tab).
MenuRadioItem
One option of a MenuRadioGroup. The chosen one shows a dot.
| Prop | Type | Default |
|---|---|---|
stringValue of the radio item. This is the value that will be set in the MenuRadioGroup when the item is selected. | No default | |
| ||
Also accepts every prop of the Base UI Menu (opens in a new tab).
MenuSeparator
A line between groups of items.
Also accepts every prop of the Base UI Menu (opens in a new tab).
MenuShortcut
Trailing text of an item, such as a shortcut or a count. Digits and symbols stay left to right.
Also accepts every prop of the Base UI Menu (opens in a new tab).
MenuSub
Groups a MenuSubTrigger and its MenuSubContent.
Also accepts every prop of the Base UI Menu (opens in a new tab).
MenuSubContent
The submenu list. It opens beside its trigger on the end side.
Also accepts every prop of the Base UI Menu (opens in a new tab).
MenuSubTrigger
An item that opens a submenu. Its chevron points to the end side and mirrors in RTL.
Also accepts every prop of the Base UI Menu (opens in a new tab).
MenuTrigger
The button that opens the menu. Render your own Button with `render`.
Also accepts every prop of the Base UI Menu (opens in a new tab).
Deviations from DGA
- Figma's hover fill is
button-background-neutral-hovered; the CSS also setsneutral-100on the same selector. Figma wins. The 2px focus border is an inset outline for keyboard focus only. - Disabled items use
text-default-disabled, notopacity: .5as in the CSS: opacity would fade the focus ring and the check mark too. - Submenus, checkbox items and radio items are added; the five trail variants are one slot (any element after the label).
| Change | Reason |
|---|---|
Menu: states and disabled | Figma is the source of truth where the CSS disagrees with itself. Opacity on the whole item also fades the focus ring. |
Menu: trailing element | One slot covers every trail variant and the extra items cover the menus that apps actually need. |
Contrast
34 pairs checked · all pass WCAG 2.1 AAShow 17 statesHide
| Variant / state | Light | Dark |
|---|---|---|
| item text / default | 18.09:1 AAA | 14.42:1 AAA |
| item icon / default | 18.09:1 3:1 | 14.42:1 3:1 |
| shortcut / default | 10.17:1 AAA | 10.95:1 AAA |
| focus ring / default | 18.09:1 3:1 | 15.89:1 3:1 |
| item text / hovered | 16.44:1 AAA | 9.59:1 AAA |
| item icon / hovered | 16.44:1 3:1 | 9.59:1 3:1 |
| shortcut / hovered | 9.24:1 AAA | 7.28:1 AAA |
| focus ring / hovered | 16.44:1 3:1 | 10.57:1 3:1 |
| item text / pressed | 14.61:1 AAA | 6.95:1 AA |
| item icon / pressed | 14.61:1 3:1 | 6.95:1 3:1 |
| shortcut / pressed | 8.22:1 AAA | 5.27:1 AA |
| focus ring / pressed | 14.61:1 3:1 | 7.66:1 3:1 |
| item text / selected | 4.53:1 AA | 8.74:1 AAA |
| item icon / selected | 4.53:1 3:1 | 8.74:1 3:1 |
| shortcut / selected | 9.72:1 AAA | 10.14:1 AAA |
| focus ring / selected | 17.29:1 3:1 | 14.72:1 3:1 |
| group label | 14.53:1 AAA | 14.42:1 AAA |
Text needs 4.5:1, icons and focus rings 3:1. Disabled states are exempt. Full contrast report