Skip to content

Split button

components specs/components/split-button.kmd

Material 3 Expressive composite button: primary action + separate menu trigger side-by-side, divided. Trailing chevron rotates + shape morphs when menu opens. 5 sizes × 4 styles. Anchored to host `buttons.kmd`.

When this spec applies

Primary triggers

All triggers

Specification body

Spec — Split button

Companion: buttons.kmd (base button variants). Trailing morph via motion.kmd R9 + shape-library.kmd R2.

Princípios

  1. Two distinct actions — primary tap = default action; trailing tap = open menu. NEVER one combined.
  2. Visual separation — divisor line entre leading + trailing.
  3. Shape morph on menu open — trailing morphs (square → pill) + chevron rotates 180°.
  4. 5 sizes, 4 styles — XS/S/M/L/XL × elevated/filled/tonal/outlined.

R1 — Anatomia

┌──────────────────┬─────┐
│  Save             │  ▼  │   ← leading (primary action) | divisor | trailing (menu)
└──────────────────┴─────┘
                          ╱
                         ╱  menu opens →
                  ┌────────────────┐
                  │ Save as...     │
                  │ Export         │
                  │ Save & close   │
                  └────────────────┘

Slots:

SlotFunction
LeadingPrimary action (text + optional icon). Tap = executa default.
Divisor1dp line; color outline-variant per color-roles.kmd.
TrailingMenu trigger (chevron ). Tap = abre menu (cross-link menus.kmd).

Both slots are focusable nodes (a11y separately reachable).

R2 — Sizes

SizeHeightPadding (L+R per slot)Min width (leading)Min width (trailing)
XS2886028
S32107232
M40168840
L482010448
XL562412056

Trailing min-width ensures chevron + tap target ≥ 28dp (S+).

R3 — Styles

Same as buttons.kmd base variants:

StyleBackgroundBorderElevation
Elevatedsurface tintnoneshadow + tonal
Filledprimarynonenone
Tonalsecondary-containernonenone
Outlinedtransparentoutline 1dpnone

Both slots share the same style (no mixed-style split buttons).

R4 — Menu open state morph

When user taps trailing → menu opens:

  1. Shape morph trailing: from square corner → pill corner (per shape-library.kmd Pill ↔ Squircle morph).
  2. Chevron rotation: rotates 180° → via spring motion-spatial-fast.
  3. Trailing color shift: optional tint to indicate active state (per preset).
  4. Menu appears: cross-link menus.kmd for menu styling.

On menu close (selection OR ESC OR outside-tap): reverse morph.

R5 — Surface bindings

SurfaceAPI
FlutterKoderSplitButton({onPrimary, menuItems, size, style}) em koder_kit/ (futuro)
Web<koder-split-button size="md" style="filled"> em koder_web_kit
Compose AndroidKoderSplitButton (futuro)
SwiftUI iOSidem (futuro)
CLI / TUIPlain action; menu via slash command (<action> default, <action>:menu to open)

R6 — Acessibilidade

  • Leading: role="button" aria-label="<primary action>".
  • Trailing: role="button" aria-haspopup="menu" aria-expanded="<state>" aria-label="<primary action> options".
  • Keyboard: Tab cycles leading → trailing; Space/Enter on each.
  • Arrow Down on trailing also opens menu.
  • ESC closes menu; focus returns to trailing.
  • Menu items per menus.kmd R6.

R7 — i18n

Inherits from buttons.kmd + menus.kmd. Trailing label localized as " options" pattern.

Keyen-USpt-BR
split_button.trailing.label"{action} options""Opções de {action}"

R8 — Reduced-motion

Shape morph + chevron rotation: instant (no spring). Menu open instant (no slide).

R9 — Per-preset variation

PresetSplit button style
material3 / material_expressiveDefault morph + rotation
material2No morph; chevron rotation only
terminal_classic[action ▾] text-only; trailing literal
brutalistSharp corners; no shape morph; no rotation animation
cyberpunk_neonDefault + glow on active state
minimalist_monoSingle bottom-border; no fill; no morph

T-suite

  • T1 Mount: render with primary + 3 menu items → leading + divisor + trailing visible.
  • T2 Primary tap: tap leading → onPrimary callback; menu NOT opened.
  • T3 Menu open: tap trailing → menu visible; trailing shape morphs; chevron rotates.
  • T4 Menu close: tap outside → menu closes; trailing reverts.
  • T5 Keyboard: Tab to leading + Enter → onPrimary; Tab to trailing + Down → menu open.
  • T6 All sizes XS-XL: render each → height correct; trailing tap-target ≥ 28dp.
  • T7 All styles 4: filled/elevated/tonal/outlined render correctly.
  • T8 Reduced-motion: morph + rotation instant.
  • T9 A11y: leading aria-label distinct from trailing.
  • N1 Click trailing accidentally → only opens menu (não executes primary action).

References