Split button
FreeA primary action joined to its alternatives; the chosen option becomes the label.
pnpm dlx shadcn@latest add https://caliperui.site/r/split-button.jsonReady to merge · 4 commits become one on main.
When to use #
Use it for
- One action is the usual choice and a few variants sit beside it, like Merge with Squash and Rebase.
- Export or publish flows where the default format is obvious and the rest are one click away.
Skip it when
- There is no default action; use a dropdown menu so every option is equal.
- There are no alternatives; use a plain button.
Installation #
pnpm dlx shadcn@latest add https://caliperui.site/r/split-button.json
Adds its 5 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage #
This is the code behind the preview above.
import { GitMerge } from "lucide-react" import { useState } from "react" import { SplitButton } from "@/components/ui/split-button" export function MergeButton() { const [status, setStatus] = useState("Ready to merge · 4 commits become one on main."); return ( <div className="grid justify-items-center gap-3"> <SplitButton menuLabel="Choose merge method" menuHeading="Merge method · remembered for halden-web" icon={<GitMerge aria-hidden="true" />} defaultValue="squash" options={[ { value: "squash", label: "Squash and merge", description: "4 commits become one on main" }, { value: "merge", label: "Create a merge commit", description: "Keeps all 4 commits and adds a merge" }, { value: "rebase", label: "Rebase and merge", description: "Unavailable: 2 files conflict with main", disabled: true }, ]} onValueChange={(value) => setStatus(value === "merge" ? "Ready to merge · keeps all 4 commits." : "Ready to merge · 4 commits become one on main.")} onRun={(value) => setStatus(`Merged #2290 into main with ${value === "merge" ? "a merge commit" : "squash"} at 14:32.`)} /> <p role="status" className="text-sm text-muted-foreground">{status}</p> </div> ); }
API reference #
SplitButton
component
Also accepts the native props of its root element and a ref.
Accessibility #
It is built on native elements and keeps aria-label and aria-hidden in step with state.
Keyboard and focus handling comes from Radix dropdown-menu: arrow keys move through items, Enter and Space select, and Escape closes and returns focus.
Motion #
It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.