Split Button
A split button is a paired control composed of a primary action button and a secondary dropdown trigger, both contained inside a single ARIA group. The primary button performs the most common action; the dropdown reveals a menu of related secondary actions.
Use it where one default action is dominant but related variants must remain discoverable — for example "Save / Save as…", "Send / Schedule send", or "Export / Export as PDF / Export as CSV".
Example
Show demo markup
<div class="split-button" aria-label="a button with a primary action and a dropdown for related actions">SplitButton</div>
Usage example
<script lang="ts">
import SplitButton from './SplitButton.svelte';
let menuOpen = $state(false);
function save() { /* … */ }
function toggleMenu() { menuOpen = !menuOpen; }
</script>
<SplitButton
label="Save options"
primaryLabel="Save"
menuLabel="More save options"
menuOpen={menuOpen}
onprimaryclick={save}
onmenutoggle={toggleMenu}
>
<ul role="menu">
<li role="menuitem">Save as draft</li>
<li role="menuitem">Save and close</li>
</ul>
</SplitButton>
Show Svelte source
// In your Svelte component:
import SplitButton from "lily-design-system-svelte-headless/components/SplitButton/SplitButton.svelte";
<SplitButton>SplitButton</SplitButton>
Usage
Save with related actions:
<SplitButton
label="Save options"
primaryLabel="Save"
menuLabel="More save options"
menuOpen={isMenuOpen}
onPrimaryClick={handleSave}
onMenuToggle={() => setIsMenuOpen(!isMenuOpen)}
>
<Menu label="More save options">
<MenuItem onclick={handleSaveAs}>Save as…</MenuItem>
<MenuItem onclick={handleSaveAndClose}>Save and close</MenuItem>
</Menu>
</SplitButton>
Disabled split button:
<SplitButton
label="Send options"
primaryLabel="Send"
menuLabel="More send options"
disabled
>
<Menu label="More send options">
<MenuItem>Schedule send</MenuItem>
</Menu>
</SplitButton>
Props
label: string (required) -- aria-label for the group
primaryLabel: string (required) -- text for the primary action button
menuLabel: string (required) -- aria-label for the dropdown trigger
menuOpen: boolean (default: false) -- whether the dropdown menu is open
disabled: boolean (default: false)
onPrimaryClick / onprimaryclick: callback -- primary action handler
onMenuToggle / onmenutoggle: callback -- menu toggle handler
children: slot -- the menu content (typically a Menu component)
...restProps: any additional HTML attributes
Keyboard Interactions
- Tab: Move focus through the primary button, then the menu trigger
- Enter / Space on the primary button: Fire
onPrimaryClick
- Enter / Space on the menu trigger: Toggle
onMenuToggle
- Escape: The consumer's menu component handles closing
ARIA
role="group" on the wrapping <div> so assistive technology treats both buttons as one composite control
aria-label on the group from the label prop
aria-haspopup="menu" on the menu trigger
aria-expanded={menuOpen} on the menu trigger
aria-label={menuLabel} on the menu trigger because it has no text content
When to Use
- Use when one primary action is dominant but secondary related actions must remain one click away.
- Use to keep the primary action a single click — split buttons preserve "default" speed while exposing alternates.
- Use for save, send, export, deploy, and similar action families.
When Not to Use
- Do not use when there is no clear primary action — use a Menu or DropdownMenu instead.
- Do not use as the only navigation pattern (use NavigationMenu, MenuBar).
- Do not use when the actions are not related — secondary items should be variants of the primary.
Headless
Provides only the structural composition (group, two buttons, hidden menu container) and the ARIA wiring. The dropdown chevron, menu positioning, focus management inside the menu, and any visual treatment are the consumer's responsibility.
Styles
Consumer CSS targets .split-button, .split-button-primary, .split-button-menu-trigger, and .split-button-menu for visual styling and positioning.
Testing
- Verify the component renders a
<div> with class split-button and role="group"
- Verify the group's
aria-label equals the label prop
- Verify the primary button renders with class
split-button-primary and text from primaryLabel
- Verify the menu trigger renders with class
split-button-menu-trigger and aria-label from menuLabel
- Verify
aria-haspopup="menu" and aria-expanded={menuOpen} on the menu trigger
- Verify the menu container has the
hidden attribute when menuOpen is false
- Verify
onPrimaryClick fires when the primary button is clicked
- Verify
onMenuToggle fires when the menu trigger is clicked
- Verify
disabled propagates to both buttons
Advice
- Designers: Make the primary action visually dominant and the dropdown trigger a smaller adjacent affordance. Keep menu items to a short list of related variants.
- Developers: The component does not manage menu state — the consumer owns
menuOpen and decides when to close (Escape, outside click, item selection). Pair with the Menu component for built-in keyboard navigation inside the menu.
Implementation Notes
- Renders a
<div> with role="group" and class="split-button"
- The group's
aria-label is set from the label prop
- The primary button:
<button class="split-button-primary" type="button">{primaryLabel}</button>
- The menu trigger:
<button class="split-button-menu-trigger" type="button" aria-haspopup="menu" aria-expanded={menuOpen} aria-label={menuLabel}></button>
- The menu container:
<div class="split-button-menu" hidden={!menuOpen}>{children}</div>
- The headless component renders no visual chevron — consumers may slot/style their own
- Keeps the primary action and dropdown as separate buttons so each has its own focus and click target
Related components
button — a generic clickable button element
dropdown-menu — a menu that opens below a trigger button
menu-bar-button — one item in a menu bar
References
- WAI-ARIA Disclosure pattern: https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/
- WAI-ARIA Menu Button pattern: https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/
- WAI-ARIA group role: https://www.w3.org/TR/wai-aria-1.2/#group
Canonical contract
Generated from this component's canonical metadata — the machine-checked source the implementations are held to.
Metadata
- Component: split-button
- PascalCase: SplitButton
- Description: a button with a primary action and a dropdown for related actions
- Status: beta — implemented and unit-tested in all seven frameworks with an axe-clean demo page; not yet exercised in composed flows
- HTML tag: <div> with role="group"
- CSS class: .split-button
- Interactive: yes
ARIA
role="group" on wrapper aria-label on group from label prop aria-haspopup="menu" on menu trigger aria-expanded={menuOpen} on menu trigger aria-label={menuLabel} on menu trigger
Keyboard
- Tab: focus primary, then trigger
- Enter / Space on primary: fires onPrimaryClick
- Enter / Space on trigger: toggles onMenuToggle
- Escape handled by consumer's menu component
Props
label: string (required) -- aria-label for the group primaryLabel: string (required) menuLabel: string (required) menuOpen: boolean (default: false) disabled: boolean (default: false) onPrimaryClick / onprimaryclick: callback onMenuToggle / onmenutoggle: callback children: slot -- menu content ...restProps: any additional HTML attributes