Split Button
A main action with a second half that opens a menu of related actions, like Save with Save as draft. Each half takes its own focus, and in Arabic the menu half moves to the left.
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/split-buttonDependencies it brings: @base-ui/react, @phosphor-icons/react
Usage
import { SplitButton, SplitButtonItem } from "@/components/ui/split-button";<SplitButton
onClick={save}
menuLabel="More save options"
menu={
<>
<SplitButtonItem onClick={saveDraft}>Save as draft</SplitButtonItem>
<SplitButtonItem onClick={publish}>Save and publish</SplitButtonItem>
</>
}
>
Save
</SplitButton>Examples
Styles
Button's filled, tonal, outline and ghost styles, with both halves always in the same one. There's no inverted-ghost and no danger version: a destructive action doesn't hide in a menu.
Sizes
Each size is as tall as the Button of the same size, and the menu half is a square. The tap target stays at least 48px at sm and xs.
Disabled
disabled switches off both halves together.
API reference
SplitButton
Other Base UI Button props (onClick, type…) go to the main action half.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | Figma's Label: the main action's text. | |
menuLabel | string | Required. The menu half's name for screen readers (“More save options”). | |
menu | ReactNode | SplitButtonItems for the related actions. | |
variant | "filled" | "tonal" | "outline" | "ghost" | "filled" | Figma's Style. |
size | "lg" | "md" | "sm" | "xs" | "lg" | Figma's Size: 56, 48, 40 and 32px. |
iconLeading | IconComponent | Icon leading: an icon before the label. | |
trailingIcon | IconComponent | CaretDownIcon | Trailing btn icon. It never mirrors. |
disabled | boolean | false | State = Disabled on both halves. |
className | string | Classes for the outer group. |
SplitButtonItem
A Base UI Menu item; takes onClick and disabled. The menu look is provisional until Figma's menu page is ready.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | The action's text. | |
onClick | (event) => void | Called on selection; the menu then closes. | |
disabled | boolean | false | Disables the item and keeps it visible. |
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 |
|---|---|
| Tab | Moves from the main action to the menu half; each half shows its own ring. |
| EnterSpace | Runs the main action, or opens the menu from the menu half. |
| ArrowDownArrowUp | Opens the menu and moves between its items. |
| Escape | Closes the menu and returns focus to the menu half. |
ARIA
- Two native buttons in a
role="group"; the menu half carriesaria-haspopup="menu"andaria-expanded. menuLabelis required by the type, so a caret never ships without a name. Write it in the page language.- The menu is Base UI's
role="menu"withmenuitems, arrow navigation and typeahead.
Arabic and RTL notes
Mirrors
- The menu half sits at the inline end: the left in Arabic, with no RTL prop.
- The menu aligns to the button's start (the right in Arabic) and takes the button's direction and language, even inside an Arabic island on an English page.
Does not mirror
CaretDownpoints down, so it never mirrors, even if you swap in a directional icon.
- Numerals
- Numbers in the label or menu items (“Export 3 files”) are formatted with
formatNumber. - Arabic typography
- Labels are
body-16(body-14atxs) Semibold like Button; menu items arebody-16in 48px rows that fit Arabic. - Mixed-direction text
- A Latin format or product name at the end of an Arabic item (“تصدير بصيغة PDF”) reads correctly; in the middle, wrap it in
<bdi>.