Menu
StableA button that opens a list of actions, with full keyboard support.
Also known as dropdown menu, context menu, action menu, overflow menu
Install with npm install @tattva/ui (setup), or copy the code from the kit in Downloads.
Icon-only trigger
<Menu
triggerLabel="More actions"
trigger={<MoreIcon width={16} height={16} />}
items={[
{ id: "copy", label: "Copy link", icon: <CopyIcon width={16} height={16} />, onSelect: () => setLast("Copy link") },
{ id: "rename", label: "Rename", icon: <EditIcon width={16} height={16} />, onSelect: () => setLast("Rename") },
{ id: "share", label: "Share", icon: <ShareIcon width={16} height={16} />, onSelect: () => setLast("Share") },
]}
/>triggerLabel gives the icon button its name. The menu opens below and lines up with the start of the button.
When to use it
Holds actions that would crowd the screen, such as Rename, Share and Delete. Choosing an item runs it and closes the menu.
Made of: Trigger button · Menu panel · Group label · Item · Icon · Shortcut · Separator
Use it for
- A More actions button on a card or message.
- A short list of related actions behind one button.
- Actions with shortcuts, a dangerous action set apart at the bottom.
Not for
- Panels with forms, text or other controls: use Popover.
- Choosing one value that stays selected: use SegmentedControl.
- The one main action on a screen: use Button.
Do and avoid
Do
- Keep the list short, and put the most used items first.
- Put dangerous actions last, after a separator.
- Use verbs for labels, such as Rename and Export.
- Confirm a destructive action in a follow-up step if it cannot be undone.
Avoid
- Do not put forms, long text or other controls in a menu. Use a Popover.
- Do not use a menu for two choices. Show two buttons.
- Do not rely on red alone. A danger item must say what it does.
- Do not nest menus inside menus.
On a phone
- On a phone the menu opens as a bottom sheet with a backdrop, in reach of the thumb; set phoneSheet to false to keep it anchored to the trigger.
- The trigger and every item are at least 44px tall on touch screens.
- Keyboard shortcuts shown beside items are hints for keyboards, so do not make an action available only through a shortcut.
Properties
| Prop | Type | Default | Description |
|---|---|---|---|
| triggerrequired | ReactNode | none | Content of the trigger button: text, an icon, or both. |
| triggerLabel | string | none | Accessible name of the trigger button. Set it whenever the trigger is only an icon. |
| label | string | none | Accessible name of the menu list. Falls back to triggerLabel, then "Menu". |
| itemsrequired | ({ type?: "item"; id: string; label: string; icon?: ReactNode; shortcut?: string; disabled?: boolean; danger?: boolean; onSelect: () => void } | { type: "separator" } | { type: "label"; label: string })[] | none | Menu content in order. Items run onSelect and then close the menu. |
| align | "start" | "end" | "start" | Which edge of the trigger the menu lines up with. |
| side | "bottom" | "top" | "bottom" | Preferred side. Popover flips it when there is no room. |
| portal | boolean | false | Draws the menu on the page itself so a scrolling or clipped parent cannot cut it off. Use it for menus inside lists and side bars. |
| phoneSheet | boolean | true | On a phone the menu opens as a bottom sheet with a backdrop, within thumb reach. Set false to keep a small floating menu there too. |
| className | string | none | Classes for the wrapper around the trigger and menu. |
"Show code under the preview gives the code for the example on the stage."
In Figma
In Figma, one Menu switches all of this from its properties panel, across 4 variants. Each property sets the React prop beside it, so design and code use the same names.
| Figma property | React | Values |
|---|---|---|
| State | Hover, Pressed and Focus come from the browser. | Default, Hover, Pressed, Focus |
States
States this part has beyond its default look. Build each one your screen can reach. States set by a property are in the Code tab.
- open
- The trigger reports aria-expanded and the list shows below or above it. Focus moves to an item.
- closing
- After it closes the list fades out in --dur-fast and cannot be pressed meanwhile. Under reduced motion it goes at once.
- disabled item
- An item with disabled looks dimmed, does nothing, is skipped by the arrow keys and is announced as disabled.
- danger item
- An item with danger uses the danger text colour and keeps its words.
- hover
- An enabled item gets a soft background under the pointer.
- focus
- The focused item gets a soft background and a visible focus ring.
Tokens
Change one of these and every part that uses it follows.
--surface--surface-raised--surface-hover--border--fg--fg-subtle--danger-fg--dur-fast--focus-ring--radius-control
Built in
- The trigger is a button with aria-haspopup="menu" and aria-expanded. The list has role menu and a name, and items have role menuitem.
- Enter, Space and Down Arrow on the trigger open the menu and focus the first item. Up Arrow opens it and focuses the last.
- Up and Down Arrow move between items and wrap. Home and End jump to the first and last. Typing letters jumps to an item that starts with them.
- Enter or Space on an item runs it and closes the menu. Escape closes it and returns focus to the trigger. Tab closes it and moves on.
- Disabled items are skipped by the arrow keys and are marked aria-disabled, so they are still announced.
- Group labels are plain text and are not focusable. Separators have role separator.
- Items are 44px tall and the trigger is 44px on touch screens.
- Danger items keep their words and also use the danger text colour.
- Closing is a quick plain fade in --dur-fast, faster than opening. The menu cannot be pressed while it fades. When people turn off motion in their system settings, it goes at once.
What you need to do
- When the trigger is only an icon, set triggerLabel to the action it opens, such as "More actions".
- Write item labels as actions, such as "Delete chat". Danger styling is colour on top of the words, never instead of them.
- Do not hide an action only in a menu if people need it often. Show it on the page as well.
- The shortcut text is only a hint. You must wire the shortcut yourself.
Built toward WCAG 2.2 AA and checked automatically; no independent audit yet. Read the accessibility guide