MD3
Expressive
MATERIAL DESIGN 3 EXPRESSIVE

Icon Buttons

Icon buttons let people take action with a single icon.

Icon buttons let people take action with a single icon. They are compact, high-density components used for the most frequent actions — like liking, bookmarking, or sharing. They morph in shape and color to communicate context and state.

Introduction

The MD3 Expressive Icon Button is a refined version of the standard button, optimized for density and clarity. It supports four primary color styles (Standard, Filled, Tonal, and Outlined) and can function as a simple action or a stateful toggle. In the Expressive update, icon buttons feature smooth shape-morphing transitions that provide tactile confirmation of interaction.

Anatomy

  • Container: The surface of the button. Supports multiple shapes (round, square) and variants.
  • Icon: The visual indicator of the action. Should be universally understood.
  • State Layer: Handles hover and pressed states with a ripple effect and morphing.
  • Selection Indicator (Toggle): In toggle mode, the container color or icon fill changes to indicate the active state.

Variants

Color Styles

  • Filled: High emphasis. Best for the primary action in a section.
  • Tonal: Medium-high emphasis. A softer alternative to Filled.
  • Outlined: Medium emphasis. Uses a border for visual separation.
  • Standard: Low emphasis. Ideal for toolbars and overflow menus.
Loading demo...

Toggle Mode

Icon buttons can toggle between two states. When selected, the button can morph its shape (e.g., from round to rounded-square) and its color to provide clear feedback.

Loading demo...

Selected Icon

Instead of conditional rendering in children, use the selectedIcon prop to render an active icon variant (such as filled) automatically when selected={true}.

Loading demo...

Features

Shapes

Choose between Round (classic) and Square (modern/expressive) base shapes. Both support morphing on interaction.

Loading demo...

Sizing

Supports five sizes (XS to XL), allowing you to match the density of your interface.

Loading demo...

Width Variants

In MD3 Expressive, icon buttons support three container width variants (default 1:1, narrow, wide) to fit different interface layouts and spatial contexts.

Loading demo...

Shape Morphing (morphRadius)

In MD3 Expressive, icon buttons feature spring-animated shape morphing on interaction. By default (morphRadius={true}):

  • On press (whileTap), the container radius compresses (e.g. 28px → 12px for md).
  • On toggle (variant="toggle" and selected={true}), the shape flips between round and squarer shapes.

You can customize or disable this behavior using the morphRadius prop:

  • morphRadius={false}: Disables all shape morphing, maintaining a fixed border radius across all states.
  • morphRadius={{ rest, hover, pressed, selected }}: Granular per-state control using numeric pixel values or MD3 token names ("none", "small", "medium", "large", "extraLarge", "full").
{/* Disable morphing — fixed round shape */}
<IconButton aria-label="Refresh" morphRadius={false}>
  <Icon name="refresh" />
</IconButton>

{/* Granular per-state morphing */}
<IconButton 
  aria-label="Settings" 
  morphRadius={{ rest: "medium", hover: "extraLarge", pressed: "small" }}
>
  <Icon name="settings" />
</IconButton>

Loading State

Displays a loading indicator while a process is in progress, automatically disabling interaction and updating ARIA states.

Loading demo...

Usage

Basic Action

import { Icon } from "@bug-on/m3-expressive/core";
import { IconButton } from "@bug-on/m3-expressive/buttons";

<IconButton aria-label="Share content" onClick={() => handleShare()}>
  <Icon name="share" />
</IconButton>

Toggle Button

const [liked, setLiked] = useState(false);

<IconButton
  variant="toggle"
  selected={liked}
  onClick={() => setLiked(!liked)}
  selectedIcon={<Icon name="favorite" fill={1} />}
  aria-label="Like"
  colorStyle="tonal"
>
  <Icon name="favorite" />
</IconButton>

Width Variants

<IconButton aria-label="Filters" width="wide">
  <Icon name="tune" />
</IconButton>

Next.js Integration (asChild)

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

import Link from "next/link";
import { Icon } from "@bug-on/m3-expressive/core";
import { IconButton } from "@bug-on/m3-expressive/buttons";

<IconButton asChild aria-label="Settings">
  <Link href="/settings">
    <Icon name="settings" />
  </Link>
</IconButton>

Best Practices

Do

  • Always provide a descriptive aria-label since the button has no text.
  • Use tooltips to explain the action if the icon is not universally known.
  • Use the Standard variant for low-priority actions in a toolbar.
  • Ensure the icon is centered perfectly within the container.

Don't

  • Don't use more than one Filled icon button in a single area.
  • Avoid using very small sizes (XS) for primary actions on touch devices.
  • Don't use complex or ambiguous icons; stick to well-known symbols.

Accessibility

  • Required Label: aria-label must be provided for every icon button.
  • Toggle State: Uses aria-pressed for toggle variants.
  • Touch Target: Even smaller visual sizes maintain a minimum 48x48dp touch target.
  • Motion: Morphing animations respect prefers-reduced-motion.

API Reference

IconButton

PropTypeDefaultDescription
childrenReactNode—Required. The icon content to display inside the button.
aria-labelstring—Required. Accessible label for screen readers.
colorStyle"standard" | "filled" | "tonal" | "outlined""standard"Visual color style role.
variant"default" | "toggle""default"Component behavior mode. Use "toggle" for stateful buttons.
selectedboolean—Current selection state. Required when variant="toggle".
selectedIconReactNode—Optional active icon to display when variant="toggle" and selected={true}.
size"xs" | "sm" | "md" | "lg" | "xl""sm"Physical container dimensions (height/diameter).
width"narrow" | "default" | "wide""default"Container slot width ratio per MD3 Expressive specification (MD3SlotWidth).
shape"round" | "square""round"Base container shape (round = CornerFull, square = CornerMedium to ExtraLarge).
morphRadiusboolean | IconButtonMorphRadiusConfigtrueControls spring-animated border-radius morphing on interaction (whileTap pressed radius & toggle selected squircle shape). Set to false to disable morphing.
loadingbooleanfalseReplaces icon with animated loading indicator and disables interaction.
loadingVariant"loading-indicator" | "circular""loading-indicator"Spinner style shown while loading={true}.
iconSizenumber | "inherit"—Explicit icon size override in px.
asChildbooleanfalseRenders as the child element (e.g., Next.js Link) while keeping styles and interactions.
classNamestring—Custom CSS classes.

Types

export type MorphRadiusLevel =
  | "none"
  | "extraSmall"
  | "small"
  | "medium"
  | "large"
  | "largeIncreased"
  | "extraLarge"
  | "extraLargeIncreased"
  | "extraExtraLarge"
  | "full";

export interface IconButtonMorphRadiusConfig {
  /** Resting border radius in px or token name */
  rest?: MorphRadiusLevel | number;
  /** Hover border radius in px or token name */
  hover?: MorphRadiusLevel | number;
  /** Pressed / whileTap border radius in px or token name */
  pressed?: MorphRadiusLevel | number;
  /** Selected toggle border radius in px or token name */
  selected?: MorphRadiusLevel | number;
}