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.
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.
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}.
Features
Shapes
Choose between Round (classic) and Square (modern/expressive) base shapes. Both support morphing on interaction.
Sizing
Supports five sizes (XS to XL), allowing you to match the density of your interface.
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.
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 formd). - On toggle (
variant="toggle"andselected={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.
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-labelsince 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-labelmust be provided for every icon button. - Toggle State: Uses
aria-pressedfor toggle variants. - Touch Target: Even smaller visual sizes maintain a minimum 48x48dp touch target.
- Motion: Morphing animations respect
prefers-reduced-motion.
API Reference
IconButton
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Required. The icon content to display inside the button. |
aria-label | string | — | 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. |
selected | boolean | — | Current selection state. Required when variant="toggle". |
selectedIcon | ReactNode | — | 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). |
morphRadius | boolean | IconButtonMorphRadiusConfig | true | Controls spring-animated border-radius morphing on interaction (whileTap pressed radius & toggle selected squircle shape). Set to false to disable morphing. |
loading | boolean | false | Replaces icon with animated loading indicator and disables interaction. |
loadingVariant | "loading-indicator" | "circular" | "loading-indicator" | Spinner style shown while loading={true}. |
iconSize | number | "inherit" | — | Explicit icon size override in px. |
asChild | boolean | false | Renders as the child element (e.g., Next.js Link) while keeping styles and interactions. |
className | string | — | 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;
}