MD3
Expressive
MATERIAL DESIGN 3 EXPRESSIVE

Buttons

Buttons let people take action and make choices with one tap. They communicate actions that users can take and are typically placed throughout the UI.

Buttons help people take the most important actions. They communicate calls to action on forms, dialogs, and pages. Expressive buttons feature spring-based shape morphing that communicates interactivity through motion.

Introduction

Material Design 3 Expressive buttons are more than just clickable areas; they are interactive elements that provide tactile feedback through motion. They support five distinct visual styles (elevated, filled, tonal, outlined, and text) and five sizes (XS to XL) to fit any context from compact mobile views to large spatial interfaces.

Anatomy

  • Container: The boundary of the button that holds all elements. Supports shape morphing from pill to rounded-square.
  • Icon (Optional): A visual cue for the action. Can be leading (before label) or trailing (after label).
  • Label Text: The primary call to action text.
  • State Layer (Ripple): A visual representation of the touch/click interaction using MD3 expressive ripple.
  • Loading Indicator: Replaces the icon or label when the button is in a loading state.

Variants

Color Styles

Buttons support 5 color styles following MD3 color roles:

  • Filled: High emphasis, for the primary action.
  • Elevated: High emphasis, used when the background is already primary-colored.
  • Tonal: Medium emphasis, for secondary actions.
  • Outlined: Medium-low emphasis, for actions that are important but not primary.
  • Text: Low emphasis, for supplementary actions.
Loading demo...

Toggle Button

Toggle buttons morph from round (unselected) to square (selected) using Expressive shape animation. This provides a clear visual distinction between states beyond just color changes.

Loading demo...

Selected Icon

Toggle buttons can specify a distinct selectedIcon (such as a filled variant) to render automatically when selected (variant="toggle" and selected={true}).

Loading demo...

Features

Loading States

Buttons support two loading variants to maintain layout stability during asynchronous actions:

  • Loading Indicator: Uses MD3 expressive shape morphing (ideal for AI generation, long-running tasks).
  • Circular: Traditional spinner (ideal for quick form submissions).
Loading demo...

Expressive Sizing

Following the May 2025 MD3 Expressive spec, buttons scale from XS (32dp) for dense interfaces to XL (136dp) for hero sections and spatial UI.

Loading demo...

Usage

Basic Usage

import { Button } from "@bug-on/m3-expressive";

<Button colorStyle="filled">Save Changes</Button>

With Icons

Icons help communicate the button's action and can be placed before or after the label.

import { Button, Icon } from "@bug-on/m3-expressive";

<Button icon={<Icon name="add" />}>Create</Button>
<Button icon={<Icon name="send" />} iconPosition="trailing">Send</Button>

Toggle Button with Selected Icon

Use selectedIcon to automatically switch the icon when the button is selected.

import { Button, Icon } from "@bug-on/m3-expressive";

<Button
  variant="toggle"
  selected={bookmarked}
  icon={<Icon name="bookmark" />}
  selectedIcon={<Icon name="bookmark" fill={1} />}
  onClick={() => setBookmarked(!bookmarked)}
>
  {bookmarked ? "Bookmarked" : "Bookmark"}
</Button>

Next.js Integration (asChild)

Use the asChild prop to render a different element (like a Next.js Link) while maintaining Button styling and animations.

import Link from "next/link";
import { Button } from "@bug-on/m3-expressive";

<Button asChild colorStyle="tonal">
  <Link href="/dashboard">Go to Dashboard</Link>
</Button>

Full Width

Use fullWidth to stretch the button to fill 100% of its parent container's width.

import { Button } from "@bug-on/m3-expressive";

<Button fullWidth colorStyle="filled">
  Full Width Action
</Button>

Button Distribute

ButtonDistribute provides a group container capable of dynamic size allocation and Expressive Spring motion transitions based on 3 modes: dynamic, fixed, and mixed. Interactive buttons within the container feature synchronized shape morphing (border radius changes down to pressedRadius on press or click) alongside spring width expansion. It is designed for media player controls, action bars, and navigation groups.

Dynamic Mode

In dynamic mode, the total group width fills 100% of the container. Hovering or focusing a button expands its width, causing neighboring buttons to shrink proportionally while maintaining full container width.

Loading demo...
import { Button, ButtonDistribute, Icon, IconButton } from "@bug-on/m3-expressive";

<ButtonDistribute mode="dynamic" weights={[1, 2, 1]} size="md">
  <IconButton aria-label="Previous">
    <Icon name="skip_previous" />
  </IconButton>
  <Button colorStyle="filled" icon={<Icon name="play_arrow" />}>
    Play
  </Button>
  <IconButton aria-label="Next">
    <Icon name="skip_next" />
  </IconButton>
</ButtonDistribute>

Fixed Mode

In fixed mode, buttons maintain their standard base size. Interacting with a button triggers state morphing while pushing neighboring buttons with subtle smooth motion.

Loading demo...
import { Button, ButtonDistribute, Icon, IconButton } from "@bug-on/m3-expressive";

<ButtonDistribute mode="fixed" size="md">
  <IconButton aria-label="Favorite" colorStyle="tonal">
    <Icon name="favorite" />
  </IconButton>
  <Button colorStyle="filled" icon={<Icon name="share" />}>
    Share
  </Button>
  <IconButton aria-label="Bookmark" colorStyle="tonal">
    <Icon name="bookmark" />
  </IconButton>
</ButtonDistribute>

Mixed Mode

In mixed mode, leading and trailing buttons maintain fixed dimensions (configurable via leadingSize / trailingSize), while the center button(s) dynamically flex to fill all remaining space (flex: 1).

Loading demo...
import { Button, ButtonDistribute, Icon, IconButton } from "@bug-on/m3-expressive";

<ButtonDistribute mode="mixed" leadingSize={56} trailingSize={56} size="md">
  <IconButton aria-label="Back" colorStyle="tonal">
    <Icon name="arrow_back" />
  </IconButton>
  <Button colorStyle="filled" icon={<Icon name="search" />}>
    Search Library
  </Button>
  <IconButton aria-label="More options" colorStyle="tonal">
    <Icon name="more_vert" />
  </IconButton>
</ButtonDistribute>

Best Practices

Do

  • Use Filled buttons for the most important action on a page.
  • Keep button labels short and action-oriented (e.g., "Save", "Delete").
  • Use icons to reinforce meaning, especially for common actions like "Search" or "Add".
  • Ensure buttons have enough spacing around them to avoid accidental taps.

Don't

  • Don't use too many high-emphasis (Filled/Elevated) buttons on a single screen.
  • Don't use a button for a simple navigation link if it doesn't represent a primary "action".
  • Don't use purely decorative icons that don't relate to the action.

Design Tokens

Corner Radius Scale

The MD3 Expressive shape system uses a 10-level corner radius scale to communicate hierarchy and state.

LevelTokenValue
None--md-sys-shape-corner-none0px
Extra Small--md-sys-shape-corner-extra-small4px
Small--md-sys-shape-corner-small8px
Medium--md-sys-shape-corner-medium12px
Large--md-sys-shape-corner-large16px
Large Increased--md-sys-shape-corner-large-increased20px
Extra Large--md-sys-shape-corner-extra-large28px
Extra Large Increased--md-sys-shape-corner-extra-large-increased32px
Extra Extra Large--md-sys-shape-corner-extra-extra-large48px
Full--md-sys-shape-corner-full9999px

Button Sizes

SizeHeightMin WidthIcon Size
XS32dp64dp18dp
SM40dp80dp20dp
MD56dp112dp24dp
LG96dp176dp32dp
XL136dp224dp40dp

Accessibility

  • Keyboard Support: Full tab order support. Space and Enter trigger the onClick event.
  • ARIA Roles: Renders as a semantic <button>. Toggle buttons use aria-pressed.
  • Busy State: loading={true} automatically sets aria-busy="true" and aria-disabled="true".
  • Touch Targets: Small buttons (XS/SM) automatically include an invisible 48x48dp touch target expander.

API Reference

Button

PropTypeDefaultDescription
colorStyle"elevated" | "filled" | "tonal" | "outlined" | "text""filled"Visual variant determining color and elevation.
variant"default" | "toggle""default"Set to "toggle" for shape-morphing toggle behavior.
selectedbooleanfalseControls toggle state (requires variant="toggle").
selectedColorStylestring"filled"Deprecated: Use colorStyle instead; toggle colors are now auto-mapped.
size"xs" | "sm" | "md" | "lg" | "xl""sm"Button height and padding scale.
shape"round" | "square""round"Override corner radius.
outlineWidthnumberOverride border width for outlined buttons.
iconReactNodeIcon to display inside the button.
selectedIconReactNodeIcon to display when variant="toggle" and selected={true}.
iconPosition"leading" | "trailing""leading"Icon placement relative to the label.
loadingbooleanfalseShows a loading indicator and blocks interaction.
loadingVariant"loading-indicator" | "circular""loading-indicator"Style of the loading indicator.
asChildbooleanfalseRenders as the child element while keeping styles.
fullWidthbooleanfalseStretches the button to fill 100% of its parent container width.
disabledbooleanfalseDisables the button.
onClick() => voidClick handler.

ButtonDistribute

PropTypeDefaultDescription
mode"dynamic" | "fixed" | "mixed""dynamic"Distribution mode for button sizing & layout behavior.
weightsnumber[][1, 2, 1]Weight ratios for items in dynamic mode.
expandRationumber0.3Expansion factor added to active item weight on hover/focus.
expandOnHoverbooleanfalseControls whether hover triggers item expansion in dynamic mode.
fixedSizenumber | string48Fixed size (in px) for leading/trailing buttons in mixed mode.
leadingSizenumber | stringExplicit fixed size for leading button in mixed mode.
trailingSizenumber | stringExplicit fixed size for trailing button in mixed mode.
gapstring | number"0.5rem"Spacing gap between buttons in group.
size"xs" | "sm" | "md" | "lg" | "xl""sm"Size token applied to child buttons.
itemClassNamestringCustom CSS class applied to item wrappers.