Split Button ported
A primary action with a menu of nearby alternatives.
A primary action joined to a chevron trigger that opens a floating dropdown of related secondary commands.
Loading demo…
Guidance
When to use
- One default action with a few close variants, like Merge with Squash and Rebase.
- Export or share actions where one format is the usual pick and others sit behind the chevron.
- Copy actions that swap the label to Copied in place while offering alternatives.
When not to use
- Use Dropdown Menu when there is no default action and every option is equal.
- Use Button when there are no alternatives.
- Use Context Menu for actions tied to a piece of content rather than a toolbar.
pnpm dlx shadcn-svelte@latest add @arcui/split-buttonUsage
<script lang="ts">
import SplitButton from '$lib/components/split-button/split-button.svelte';
import GitMerge from '@lucide/svelte/icons/git-merge';
import GitPullRequest from '@lucide/svelte/icons/git-pull-request';
function merge() { console.log('Merged'); }
function squash() { console.log('Squashed'); }
function rebase() { console.log('Rebased'); }
</script>
{#snippet mergeIcon()}<GitMerge size={15} />{/snippet}
<SplitButton
label="Merge pull request"
icon={mergeIcon}
onClick={merge}
actions={[
{ label: 'Squash and merge', onSelect: squash },
{ label: 'Rebase and merge', onSelect: rebase },
{ label: 'Close pull request', destructive: true, onSelect: () => console.log('Closed') }
]}
/>Variants & Examples
Primary Variant
High-contrast primary action button joined with dropdown trigger.
<SplitButton label="Publish" variant="primary" onClick={publish} actions={publishActions} />Secondary Variant
Muted border variant for secondary workflows like Export and Share.
<SplitButton label="Export" variant="secondary" onClick={exportData} actions={exportActions} />Disabled State
Disables both the primary action and the dropdown chevron trigger.
<SplitButton label="Merge" disabled actions={actions} />API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Main action label. In Phase 2, text changes morph letter by letter. |
actions | SplitButtonAction[] | — | Menu items: { label, onSelect?, disabled?, destructive?, icon?: Snippet }. |
onClick | () => void | — | Runs the primary action. |
icon | Snippet | — | Optional leading icon snippet on the primary half. |
variant | 'primary' | 'secondary' | 'primary' | Visual weight of both button halves. |
disabled | boolean | false | Disables both the main action and the menu trigger. |
Keyboard Interactions
| Key | Action |
|---|---|
Enter / Space | Runs the main action, or opens the menu from the chevron. |
ArrowDown / ArrowUp | Moves between menu items, looping at the ends. |
Escape | Closes the menu and returns focus cleanly to the chevron. |
Accessibility
- Two native button elements; the chevron is labelled "<label> more actions".
- The menu is powered by Bits UI DropdownMenu with WAI-ARIA menu and menuitem roles and roving focus management.
- The main label is announced through an aria-live="polite" region when it changes.
Motion
Phase 1 still-state: primary slot width is measured statically and label renders plain string without character morphing. Phase 2 springs width with motionTokens.spring.morph and wires MorphText letter diffing. Reduced motion drops transitions and swaps states instantly.
Notes for AI
- Use when one action is the default and two to five close variants exist. For a menu with no default action use Dropdown Menu.
- Keep the menu actions as variations of the main action; put unrelated commands elsewhere.
Source of truth: registry/components/split-button/split-button.tsx