MD3
Expressive
MATERIAL DESIGN 3 EXPRESSIVE

Split Buttons

Split buttons let people take a primary action and choose from a set of related secondary actions.

Split buttons consist of a leading button for the primary action and a trailing toggle for secondary options. They provide a compact way to group related actions while maintaining a clear primary call to action.

Loading demo...

Introduction

Material Design 3 Expressive split buttons are advanced interactive elements designed with high-fidelity motion physics and shape-morphing capabilities. They provide tactile feedback and maintain visual continuity by synchronizing the inner corner radii of both buttons during state transitions.

Anatomy

  • Leading Button: The primary action element. Supports text labels and leading icons.
  • Trailing Button: A toggle or action button for secondary options. Features a rotating chevron or custom icon.
  • Separator: A 2px visual gap between the two buttons, ensuring they feel connected but distinct.
  • Shared Morphing: The inner corners of both buttons utilize synchronized spring physics to adjust their radius during hover, focus, and selection states.

Variants

Color Styles

Split buttons support four main visual variants to accommodate different levels of hierarchy:

  • Filled: High emphasis, for the primary set of actions.
  • Tonal: Medium emphasis, for secondary flows or less prominent actions.
  • Elevated: High emphasis, used on surface backgrounds to provide depth.
  • Outlined: Medium-low emphasis, for actions that need clear boundaries without overwhelming the layout.
Loading demo...

Uncheckable Variant

The trailing button can also be used as a simple secondary action button without a persistent toggle state. This is useful for immediate actions like "Schedule" or "Delete".

Loading demo...

Features

Expressive Motion

This component utilizes a dual-motion scheme optimized for both aesthetics and utility:

  • Spatial Spring: Used for the container's shape morphing (border-radius). It uses a fast, bouncy spring to create a tactile feel.
  • Standard Motion: Used for the trailing icon rotation. This ensures functional clarity and precision when opening menus.

Responsive Scaling

Following the MD3 Expressive specification, split buttons scale across five sizes to support dense mobile layouts and large spatial UIs:

SizeHeightUse Case
XS32dpDense data tables or toolbars
SM40dpDefault mobile and web interfaces
MD56dpDesktop-first or high-density layouts
LG96dpHero moments and large spatial displays
XL136dpLarge immersive experiences
Loading demo...

Usage

Basic Usage

import { 
  SplitButtonLayout, 
  SplitButtonLeading, 
  SplitButtonTrailing 
} from "@bug-on/m3-expressive";

const [checked, setChecked] = React.useState(false);

<SplitButtonLayout
  variant="filled"
  leadingButton={<SplitButtonLeading>Save</SplitButtonLeading>}
  trailingButton={
    <SplitButtonTrailing
      checked={checked}
      onCheckedChange={setChecked}
      aria-label="More save options"
    />
  }
/>

The Split Button is most commonly used as a trigger for a Menu. Synchronizing the checked state of the trailing button with the open state of the menu ensures a smooth expressive morphing transition.

Loading demo...

Best Practices

Do

  • Align the menu with the trailing button to maintain visual context.
  • Use Large or Extra Large sizes for primary "Hero" actions on landing pages.
  • Provide a descriptive aria-label for the trailing button to explain what options it reveals.

Don't

  • Don't use very long labels for the leading button; keep it to one or two words.
  • Don't change the trailing icon if it's used for a menu; the rotating chevron is a standard affordance.

Design Tokens

Corner Radius

Split buttons utilize a complex inner corner radius that adjusts based on state to create the expressive morphing effect:

SizeDefaultHovered/Pressed
XS4dp8dp (Small)
SM / MD4dp12dp (Medium)
LG8dp28dp (Large+)
XL12dp28dp (Large+)

The outer corners always maintain a Full Pill (50%) shape to ensure a consistent silhouette.

Motion

AnimationSpring TokenTier
Shape/SizeFAST_SPATIAL_SPRINGFast
Icon RotationSTANDARD_EASINGFunctional

Accessibility

  • Keyboard Support: Navigate between buttons using Tab. Activate the primary action with Enter and the menu toggle with Space or Enter.
  • ARIA Roles: The container uses role="group" to associate related actions. The trailing button includes aria-haspopup="menu" (customizable) and aria-expanded (via checked) for screen reader compatibility.
  • Reduced Motion: All spring animations automatically scale down or disable when the user prefers reduced motion.

API Reference

SplitButtonLayout

PropTypeDefaultDescription
leadingButtonReactNodeThe primary action button.
trailingButtonReactNodeThe secondary action or toggle component.
variant"filled" | "tonal" | "elevated" | "outlined"Visual variant for both buttons. Automatically passed down to sub-components via Context.
size"xs" | "sm" | "md" | "lg" | "xl"Size for both buttons. Automatically passed down to sub-components via Context.
spacingnumber2Gap between buttons in pixels.
classNamestringCustom styling classes.

SplitButtonLeading (and variants)

PropTypeDefaultDescription
variant"filled" | "tonal" | "elevated" | "outlined""filled"Visual variant.
size"xs" | "sm" | "md" | "lg" | "xl""sm"Component size.
iconReactNodeLeading icon.
disabledbooleanfalseDisables the button.
asChildbooleanfalseRenders as the child element while keeping styles.

SplitButtonTrailing (and variants)

PropTypeDefaultDescription
checkedbooleanfalseToggle state (sync with menu open state).
onCheckedChange(checked: boolean) => voidCallback for state change.
iconReactNodearrow_drop_downIcon to display (rotates when checked).
size"xs" | "sm" | "md" | "lg" | "xl""sm"Component size.
aria-labelstringRequired. Label for screen readers.
aria-haspopupboolean | "menu" | "listbox" | "tree" | "grid" | "dialog""menu"Type of interactive popup element.
asChildbooleanfalseRenders as the child element while keeping styles.

SplitButtonTrailingUncheckable

PropTypeDefaultDescription
iconReactNodeIcon to display.
onClick() => voidClick handler.
size"xs" | "sm" | "md" | "lg" | "xl""sm"Component size.
aria-labelstringRequired. Label for screen readers.
asChildbooleanfalseRenders as the child element while keeping styles.