Skip to content

Split button

In review
Live · web

A default action welded to a menu of alternatives. Click the label, or the caret

The label fires the default directly. The caret opens the menu: Enter / opens, move, Enter runs (and that choice becomes the new default), Esc closes.

Compose · reply
Working tree

Disabled: the WHOLE control, always (a split never half-disables)

Last action: none yet · defaults: send send, commit commit · pointer + keyboard both work; the island is live.

// Web: React, consuming @dizyx/nockerl-tokens. (Web is the laggard platform. Spec + live demo,
// not yet exported from @dizyx/nockerl-react; promotion is tracked.)
// The primary segment fires `onAction` directly; only the caret opens the menu.
export function ComposeBar() {
const [defaultId, setDefaultId] = useState('send');
return (
<SplitButton
variant="primary"
leadingIcon="send"
defaultActionId={defaultId}
onAction={(id) => runSend(id)} // primary OR a picked menu item
onDefaultChange={setDefaultId} // a picked item becomes the new default
actions={[
{ id: 'send', label: 'Send now', icon: 'send' },
{ id: 'later', label: 'Send later', icon: 'clock' },
{ id: 'archive', label: 'Send & archive', icon: 'archive' },
{ id: 'draft', label: 'Save as draft', icon: 'edit', keepDefault: true },
]}
/>
);
}
PropTypeDefaultDescription
actions *SplitAction[]Ordered list of { id, label, icon?, danger?, keepDefault? }. The first is the initial default; the caret menu lists them all.
onAction *(id: string) => voidFired by the primary segment (current default) AND by a picked menu item. Ignored while disabled.
defaultActionIdstringControlled id of the action the primary segment fires. Uncontrolled if omitted (tracks the last pick).
onDefaultChange(id: string) => voidCalled when a picked item promotes itself to the new default (skipped for keepDefault items).
variant'primary' | 'secondary' | 'tertiary' | 'destructive''primary'Shared fill across both segments, following the Button emphasis ladder. One primary per view.
size'sm' | 'md''md'Control height + padding; the caret is a square of the same height.
leadingIconstringOptional glyph before the default label on the primary segment.
disabledbooleanfalseThe WHOLE control inert and clearly seen (never faded to invisible). A split never half-disables: both segments always share one state.