Build · New

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

SoonInstalling from the registry opens at launch. Until then the command below is what it will be.
npx shadcn@latest add @ritla/menu

Dependencies 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.

Submenu

Multi level: “Move to” opens a submenu at the inline end. In Arabic it opens to the left; ArrowLeft opens it and ArrowRight closes it.

API reference

Menu

The root: holds the state (open, onOpenChange, disabled, modal).

PropTypeDefaultDescription
open / defaultOpenbooleanWhether the menu is open (controlled or not).
onOpenChange(open: boolean) => voidCalled when the menu opens or closes.
disabledbooleanfalseDisables the trigger and the menu.

MenuTrigger

PropTypeDefaultDescription
renderReactElementThe visible trigger: <IconButton … /> or <Button … />. It gets aria-haspopup and aria-expanded.

MenuContent

PropTypeDefaultDescription
align"start" | "center" | "end""start"Alignment with the trigger; Figma uses the inline start.
sideOffsetnumber12The 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

PropTypeDefaultDescription
childrenReactNodeThe title (Figma Title).
leadingIconIconComponentFigma's Show leading: a 24px icon at the inline start.
descriptionReactNodeFigma's Show description: a second line under the title.
onClick(event) => voidMenuItem: called on selection; the menu then closes.
hrefstringMenuLinkItem: the link target.
disabledbooleanfalseState = 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.

KeyAction
EnterSpaceArrowDownOn the trigger: opens the menu and focuses the first item.
ArrowDownArrowUpMoves between items and wraps. A disabled item takes focus so its reason is read, but doesn't run.
ArrowRightArrowLeftOpens and closes a submenu. Flips in Arabic: ArrowLeft opens.
EnterRuns the focused item and closes the menu.
EscapeCloses the menu and returns focus to the trigger.

ARIA

  • The trigger carries aria-haspopup="menu" and aria-expanded; the menu is role="menu" with menuitems.
  • An icon trigger needs a label that says what the menu holds (“Invoice actions”), not “More”.
  • MenuLabel names 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 CaretRight flips, 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

  • DotsThreeVertical and 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-14 Medium, second lines label-12, section headers body-14 Semibold. 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>.

All components ›