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.
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.
Selected Icon
Toggle buttons can specify a distinct selectedIcon (such as a filled variant) to render automatically when selected (variant="toggle" and selected={true}).
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).
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.
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.
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.
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).
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.
| Level | Token | Value |
|---|---|---|
| None | --md-sys-shape-corner-none | 0px |
| Extra Small | --md-sys-shape-corner-extra-small | 4px |
| Small | --md-sys-shape-corner-small | 8px |
| Medium | --md-sys-shape-corner-medium | 12px |
| Large | --md-sys-shape-corner-large | 16px |
| Large Increased | --md-sys-shape-corner-large-increased | 20px |
| Extra Large | --md-sys-shape-corner-extra-large | 28px |
| Extra Large Increased | --md-sys-shape-corner-extra-large-increased | 32px |
| Extra Extra Large | --md-sys-shape-corner-extra-extra-large | 48px |
| Full | --md-sys-shape-corner-full | 9999px |
Button Sizes
| Size | Height | Min Width | Icon Size |
|---|---|---|---|
| XS | 32dp | 64dp | 18dp |
| SM | 40dp | 80dp | 20dp |
| MD | 56dp | 112dp | 24dp |
| LG | 96dp | 176dp | 32dp |
| XL | 136dp | 224dp | 40dp |
Accessibility
- Keyboard Support: Full tab order support.
SpaceandEntertrigger theonClickevent. - ARIA Roles: Renders as a semantic
<button>. Toggle buttons usearia-pressed. - Busy State:
loading={true}automatically setsaria-busy="true"andaria-disabled="true". - Touch Targets: Small buttons (XS/SM) automatically include an invisible 48x48dp touch target expander.
API Reference
Button
| Prop | Type | Default | Description |
|---|---|---|---|
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. |
selected | boolean | false | Controls toggle state (requires variant="toggle"). |
selectedColorStyle | string | "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. |
outlineWidth | number | — | Override border width for outlined buttons. |
icon | ReactNode | — | Icon to display inside the button. |
selectedIcon | ReactNode | — | Icon to display when variant="toggle" and selected={true}. |
iconPosition | "leading" | "trailing" | "leading" | Icon placement relative to the label. |
loading | boolean | false | Shows a loading indicator and blocks interaction. |
loadingVariant | "loading-indicator" | "circular" | "loading-indicator" | Style of the loading indicator. |
asChild | boolean | false | Renders as the child element while keeping styles. |
fullWidth | boolean | false | Stretches the button to fill 100% of its parent container width. |
disabled | boolean | false | Disables the button. |
onClick | () => void | — | Click handler. |
ButtonDistribute
| Prop | Type | Default | Description |
|---|---|---|---|
mode | "dynamic" | "fixed" | "mixed" | "dynamic" | Distribution mode for button sizing & layout behavior. |
weights | number[] | [1, 2, 1] | Weight ratios for items in dynamic mode. |
expandRatio | number | 0.3 | Expansion factor added to active item weight on hover/focus. |
expandOnHover | boolean | false | Controls whether hover triggers item expansion in dynamic mode. |
fixedSize | number | string | 48 | Fixed size (in px) for leading/trailing buttons in mixed mode. |
leadingSize | number | string | — | Explicit fixed size for leading button in mixed mode. |
trailingSize | number | string | — | Explicit fixed size for trailing button in mixed mode. |
gap | string | number | "0.5rem" | Spacing gap between buttons in group. |
size | "xs" | "sm" | "md" | "lg" | "xl" | "sm" | Size token applied to child buttons. |
itemClassName | string | — | Custom CSS class applied to item wrappers. |