motif svelteArc

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.

Live specimen

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.

Installation

pnpm dlx shadcn-svelte@latest add @arcui/split-button

Usage

<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

PropTypeDefaultDescription
labelstring—Main action label. In Phase 2, text changes morph letter by letter.
actionsSplitButtonAction[]—Menu items: { label, onSelect?, disabled?, destructive?, icon?: Snippet }.
onClick() => void—Runs the primary action.
iconSnippet—Optional leading icon snippet on the primary half.
variant'primary' | 'secondary''primary'Visual weight of both button halves.
disabledbooleanfalseDisables both the main action and the menu trigger.

Keyboard Interactions

KeyAction
Enter / SpaceRuns the main action, or opens the menu from the chevron.
ArrowDown / ArrowUpMoves between menu items, looping at the ends.
EscapeCloses 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