Skip to main content

Design system components

SplitButton

Primary action button paired with a dropdown toggle for related secondary actions, with collision-aware menu placement and optional nested submenus.

Status: beta. Group: actions. Markdown version: /design-system/components/split-button.md

When to use

SplitButton pairs one dominant action with a menu of close variants of that same action: Save with Save as / Save a copy, Export with format choices. Both segments share Button's weight, size, and surface so the pair reads as a single control.

When not to use

  • Do not use it for general-purpose dropdown menus without a dominant default action.
  • Avoid mixing destructive options into a constructive menu.
  • Do not use it for more than one submenu level.
  • Do not use SplitButton as a general dropdown menu; if there is no dominant default action, use a plain menu trigger instead.

Look-alikes

  • ButtonGroup: Do not put a menu trigger inside; SplitButton owns primary-action + menu.

Props

SplitButton props
PropTypeDefaultDescriptionRequired
labelReact.ReactNodenoneMain label rendered on the primary segment.Yes
onPrimaryClickReact.MouseEventHandler<HTMLButtonElement>nonePrimary action invoked when clicking the main segment.No
optionsSplitButtonOption[]noneList of alternative actions shown in the dropdown.Yes
menuAlign"end" | "start"startAlignment of the dropdown menu along the toggle edge.No
disabledbooleanfalseDisable both segments.No
toggleLabelstringnoneAccessible label for the dropdown trigger.No
classNamestringnoneOptional className forwarded to the outer wrapper.No

Variants

  • Available variants: primary, secondary, tertiary (default primary).
  • Available surfaces: default, onDark, onBrand (default default).
  • Available sizes: sm, md, lg (default md).
  • Allowed menuAlign values: end, start (default start).

Invalid combinations

The contract records no invalid combinations.

Keyboard

SplitButton keyboard behaviour
KeyBehaviour
TabBoth segments are focusable in sequence.
Enter / SpaceOn the primary segment; fires onPrimaryClick.
Enter / Space / ArrowDownOn the toggle; opens the menu and focuses the first enabled item.
ArrowDown / ArrowUpMove through enabled items (roving tabindex).
Home / EndJump to first / last enabled item.
ArrowRightOpens a submenu; ArrowLeft closes it.
EscapeCloses the menu and returns focus to the toggle.
TabInside the open menu is trapped and cycles items until Escape.

Rules

Every item under When not to use is a MUST NOT. MUST and SHOULD follow RFC 2119.

SplitButton rules
LevelRuleHow it is checked
MUSTARIA: Toggle carries aria-haspopup='menu', aria-expanded, aria-controls, and an accessible name (toggleLabel, default t('splitButton.moreOptions')).Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: Menu uses role='menu' with role='menuitem' buttons; submenu triggers add aria-haspopup and aria-expanded.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: Option icons are decorative (aria-hidden); the option label is the announcement.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTRequired stories produce no axe violations at the configured severity.Automated by npm run test:stories:matrix:ci. WCAG 2.5.8.
MUSTThe accessibility tree (role, name, state) matches the committed snapshot for every required story.Automated by npm run test:stories:matrix:ci. WCAG 1.3.1, 4.1.2.
MUSTThe component remains legible and operable under a real browser forced-colors: active pass.Automated by npm run test:stories:hc.
MUSTEvery documented keyboard interaction works as specified.Not yet verified: no evidence record. WCAG 2.1.1, 2.1.2.
MUSTA human completed an end-to-end accessibility review of this component.Manual review, last on 2026-07-03.
SHOULDMake the primary segment the safest, most common choice; the menu holds variants of it, not unrelated commands.Not stated in the contract.
SHOULDKeep options parallel in wording (Save as, Save a copy, Save to cloud) so the menu scans as one family.Not stated in the contract.
SHOULDGive every option an id; labels can be translated, ids anchor analytics and tests.Not stated in the contract.
SHOULDThe dropdown always portals (shared Menu primitive), so it never clips even inside an overflow-hidden container.Not stated in the contract.
SHOULDProvide toggleLabel when the menu is not generic more options, for example Choose export format.Not stated in the contract.
SHOULD NOTDo not mix destructive options into a constructive menu; Delete does not belong under Save.Not stated in the contract.
SHOULD NOTDo not nest submenus more than one level; the component supports exactly one level by design.Not stated in the contract.

Install and import

  • Install: npm install @digitaltableteur/react (version 0.9.1, restricted access on npm).
  • Import: import { SplitButton } from "@digitaltableteur/react";
  • Entry point: @digitaltableteur/react/actions exports it too.
  • Styles: import @digitaltableteur/react/style.css once at the app boundary.
  • Also available as the custom element <dt-split-button>; see the custom elements manifest.