SplitButton
A primary action with a menu of related ones beside it: two halves of one pill, each its own button.
import { SplitButton } from "@butteryui/react";Demo
Nothing exported yet
When to use it
Use it for
- One main action with a few close alternatives: Export, with Export as JPG and Copy to clipboard
- The footer of an inspector or a dialog where the main action has variants
Not for
- Actions that are equally likely: show them as separate buttons
- More than six alternatives, or settings: use a menu or a dialog
Examples
Export footer
An inspector's footer: Copy beside a split Export whose menu holds the other formats.
Export
Export options
Export as the main action, with other formats in the menu.
Nothing exported yet
Publish later
Publish now, or pick a later time from the menu: both halves stay disabled until the deck has a title, and one entry is unavailable.
Draft
Show new option
The menu's open state kept in the example, so a tip can open it to point out a new entry.
New: copy to the clipboard.
Nothing exported yet
Variants and states
Main action and alternatives
Each half is its own button: Tab moves between them, and the chevron opens the menu.
Nothing yet
Disabled
Props
SplitButton
| Prop | Type | Default | Description |
|---|---|---|---|
childrenrequired | ReactNode | The main half's label. | |
onClick | MouseEventHandler<HTMLButtonElement> | The main action. | |
itemsrequired | readonly SplitButtonItem[] | The related actions in the menu. | |
menuLabelrequired | string | Names the menu half for screen readers, such as "More export options". | |
disabled | boolean | Disables both halves. | |
open | boolean | Whether the menu is open. Pair with onOpenChange to control it. | |
defaultOpen | boolean | false | Whether the menu starts open when uncontrolled. Defaults to false. |
onOpenChange | ((open: boolean) => void) | Called when the menu opens or closes. |
It also takes Omit<ComponentPropsWithRef<"div">, "onClick">.
SplitButtonItem
One entry in a `SplitButton`'s menu.
| Field | Type | Default | Description |
|---|---|---|---|
labelrequired | string | What the entry says. | |
onSelectrequired | () => void | Runs when the entry is chosen. The menu closes afterwards. | |
icon | ComponentType<IconComponentProps> | An icon before the label. | |
disabled | boolean | Keeps the entry from being chosen. |
Accessibility
A group of two buttons, each a tab stop: the main half is named by its label, the chevron half by menuLabel. The chevron opens a menu: arrow keys move, Enter chooses, Escape closes and returns focus.
| Keys | What they do |
|---|---|
| Tab | Moves between the main half and the chevron half, each its own stop |
| Enter or Space | Runs the main action on the main half, or the focused entry in the menu, which then closes |
| Enter, Space or Arrow Down | Opens the menu from the chevron half, with focus on its first entry |
| Arrow Down or Arrow Up | Moves between the menu's entries, skipping disabled ones |
| Home or End | Moves to the first or last entry |
| Letters | Jumps to the entry that starts with them |
| Escape | Closes the menu and puts focus back on the chevron half |
Motion
It scales to 97% while it's pressed, over `--bt-dur-press`, and springs back when it's let go.
With reduced motion: Each half presses in at once, and the menu fades in and out over 100ms without growing.
Layout
One pill --bt-h-button (36px) tall: the main half as wide as its label and padding, then a 1px divider half the pill's height, then the chevron half, a 36px square. It's never wider than its container, where a long label truncates with an ellipsis and shows in full as its title. Each half is a hit area the pill's full height, and the menu is at least --bt-w-menu-min (200px) wide.