Menu Button
A trigger that opens a menu of actions: row actions, account menus and secondary actions. The menu opens below the trigger at the inline start, the right in Arabic.
Preview
Mirrored is the same English content laid out right to left: it separates a mirroring bug from a translation one.
Installation
npx shadcn@latest add @ritla/menuDependencies it brings: @base-ui/react, @phosphor-icons/react
Usage
import { DotsThreeVerticalIcon, PencilSimpleIcon } from "@phosphor-icons/react/ssr";
import { IconButton } from "@/components/ui/icon-button";
import { Menu, MenuContent, MenuItem, MenuTrigger } from "@/components/ui/menu";<Menu>
<MenuTrigger render={<IconButton variant="outline" icon={DotsThreeVerticalIcon} label="Invoice actions" />} />
<MenuContent>
<MenuItem leadingIcon={PencilSimpleIcon} onClick={edit}>Edit</MenuItem>
<MenuItem onClick={duplicate}>Duplicate</MenuItem>
</MenuContent>
</Menu>Examples
Split Button trigger
Figma's Button type: the Split Button's menu half opens the same menu, lined up with the whole button.
Sections and dividers
MenuGroup with a MenuLabel names each section, with MenuSeparator between them. description adds a second line, MenuLinkItem navigates, and a disabled item stays visible with its reason.
API reference
Menu
The root: holds the state (open, onOpenChange, disabled, modal).
| Prop | Type | Default | Description |
|---|---|---|---|
open / defaultOpen | boolean | Whether the menu is open (controlled or not). | |
onOpenChange | (open: boolean) => void | Called when the menu opens or closes. | |
disabled | boolean | false | Disables the trigger and the menu. |
MenuTrigger
| Prop | Type | Default | Description |
|---|---|---|---|
render | ReactElement | The visible trigger: <IconButton … /> or <Button … />. It gets aria-haspopup and aria-expanded. |
MenuContent
| Prop | Type | Default | Description |
|---|---|---|---|
align | "start" | "center" | "end" | "start" | Alignment with the trigger; Figma uses the inline start. |
sideOffset | number | 12 | The gap below the trigger, in px. |
anchor | "trigger" | "parent" | "trigger" | What the menu lines up with; parent for the whole Split Button. |
MenuItem, MenuLinkItem, MenuSubmenuTrigger
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | The title (Figma Title). | |
leadingIcon | IconComponent | Figma's Show leading: a 24px icon at the inline start. | |
description | ReactNode | Figma's Show description: a second line under the title. | |
onClick | (event) => void | MenuItem: called on selection; the menu then closes. | |
href | string | MenuLinkItem: the link target. | |
disabled | boolean | false | State = Disabled; stays visible and focusable but doesn't run. Put the reason in description. |
Accessibility
Keyboard
Keys are written for left-to-right. In right-to-left the arrow keys swap: the one pointing toward the reading direction moves forward.
| Key | Action |
|---|---|
| EnterSpaceArrowDown | On the trigger: opens the menu and focuses the first item. |
| ArrowDownArrowUp | Moves between items and wraps. A disabled item takes focus so its reason is read, but doesn't run. |
| ArrowRightArrowLeft | Opens and closes a submenu. Flips in Arabic: ArrowLeft opens. |
| Enter | Runs the focused item and closes the menu. |
| Escape | Closes the menu and returns focus to the trigger. |
ARIA
- The trigger carries
aria-haspopup="menu"andaria-expanded; the menu isrole="menu"withmenuitems. - An icon trigger needs a
labelthat says what the menu holds (“Invoice actions”), not “More”. MenuLabelnames its group, so screen readers announce the section.
Arabic and RTL notes
Mirrors
- The menu opens at the trigger's start (the right in Arabic); submenus open toward the inline end (the left).
- The submenu caret
CaretRightflips, and arrow keys follow the reading direction. - The menu takes the trigger's direction and language, even inside an Arabic island on an English page.
Does not mirror
DotsThreeVerticaland non-directional icons don't mirror.
- Numerals
- Counts in second lines (“8 members”) use
formatNumber; identifiers (“INV-1042”) go in<bdi dir="ltr">and never change. - Arabic typography
- Titles are
label-14Medium, second lineslabel-12, section headersbody-14Semibold. Rows are 48px and grow for Arabic. - Mixed-direction text
- A Latin name at the end of an Arabic item (“تنزيل PDF”) renders correctly; in the middle, wrap it in
<bdi>.