Build · New

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

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

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

Menu and keyboard

Tab moves between the halves. The menu half opens with Enter, Space or ArrowDown, and the menu lines up with the start of the whole button: the right in Arabic. Try the Arabic mode.

Disabled

disabled switches off both halves together.

API reference

SplitButton

Other Base UI Button props (onClick, type…) go to the main action half.

PropTypeDefaultDescription
childrenReactNodeFigma's Label: the main action's text.
menuLabelstringRequired. The menu half's name for screen readers (“More save options”).
menuReactNodeSplitButtonItems 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.
iconLeadingIconComponentIcon leading: an icon before the label.
trailingIconIconComponentCaretDownIconTrailing btn icon. It never mirrors.
disabledbooleanfalseState = Disabled on both halves.
classNamestringClasses 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.

PropTypeDefaultDescription
childrenReactNodeThe action's text.
onClick(event) => voidCalled on selection; the menu then closes.
disabledbooleanfalseDisables 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.

KeyAction
TabMoves from the main action to the menu half; each half shows its own ring.
EnterSpaceRuns the main action, or opens the menu from the menu half.
ArrowDownArrowUpOpens the menu and moves between its items.
EscapeCloses the menu and returns focus to the menu half.

ARIA

  • Two native buttons in a role="group"; the menu half carries aria-haspopup="menu" and aria-expanded.
  • menuLabel is 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" with menuitems, 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

  • CaretDown points 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-14 at xs) Semibold like Button; menu items are body-16 in 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>.

All components ›