Skip to content

ExpressiveCSS

Browse documentation

Icon buttons

A single icon as the whole control, in four styles and five sizes.

View as Markdown

An icon button is one action shown as one icon, such as a toolbar overflow, a dialog's close button, or the star on a row. Add icon-button to a <button>, or to an <a> when it navigates, and put a single <span class="material-symbols"> inside it.

Mark the icon aria-hidden, because it is decoration. That leaves the control without an accessible name, so every icon button needs an aria-label. SEMANTICS.md enforces both rules against the docs, and a bare icon button fails the suite.

An icon button keeps its own look inside the components that host actions: a toolbar, an app bar, a list row, a dialog header, a snackbar, a navigation rail, a pane header, or a footer. Those hosts style plain controls placed in them and leave a control that styles itself alone, as they already do for button.

This is a separate component, not a modifier on the common button. Material 3 gives it its own token families, its own size scale and its own colours, and none of them line up with a common button's. The older <button class="circle"> still works and is still the 40dp common button in a round shape. See Buttons.

<button type="button" class="icon-button" aria-label="Add">
  <span class="material-symbols" aria-hidden="true">add</span>
</button>

Styles

There are four styles, with the same modifier names as buttons. standard is the default and has no container. filled is primary, tonal is secondary-container, and outlined is an outline-variant border with no fill. Material 3 gives icon buttons no elevated style, so there is no elevated here. Standard is already what text would be.

StyleContainerIconUse it for
standard (default)noneon-surface-variantThe usual case, in toolbars, list rows and app bars.
filledprimaryon-primaryThe one high-emphasis action on the screen.
tonalsecondary-containeron-secondary-containerMedium emphasis, without the weight of a filled one.
outlinednone, outline-variant borderon-surface-variantMedium emphasis on a busy or image background.
<button type="button" class="icon-button" aria-label="Add">…</button>
<button type="button" class="icon-button filled" aria-label="Add">…</button>
<button type="button" class="icon-button tonal" aria-label="Add">…</button>
<button type="button" class="icon-button outlined" aria-label="Add">…</button>

Sizes

There are five sizes. small is the default at 40dp with a 24dp icon, so it needs no class. The rest are xsmall (32dp, 20dp icon), medium (56dp, 24dp), large (96dp, 32dp) and xlarge (136dp, 40dp). They compose with the styles: class="icon-button tonal large".

<button type="button" class="icon-button filled xsmall" aria-label="Add">…</button>
<button type="button" class="icon-button filled" aria-label="Add">…</button>
<button type="button" class="icon-button filled medium" aria-label="Add">…</button>
<button type="button" class="icon-button filled large" aria-label="Add">…</button>
<button type="button" class="icon-button filled xlarge" aria-label="Add">…</button>

Width

Add narrow to tighten the inset or wide to widen it. Both scale with whichever size they are on. Width is the inset plus the icon, so it has no size of its own. That is why the default width of every size equals its height, and why a round icon button is already a circle.

<button type="button" class="icon-button tonal medium narrow" aria-label="Star">…</button>
<button type="button" class="icon-button tonal medium wide" aria-label="Star">…</button>

Shape

Icon buttons are round by default. Add square for the squared container. Its corner grows with the size: 12dp on the two small sizes, 16dp on medium, and 28dp on the two large ones. Either way, the corner tightens while the button is pressed. This is the Material 3 Expressive shape morph.

<button type="button" class="icon-button filled square" aria-label="Edit">…</button>

Disabled

Set disabled on the <button>. The icon drops to on-surface at 38%, and a style that has a container drops it to 10%.

An <a href> cannot be disabled, and no class makes it so. pointer-events: none only blocks the pointer. The link stays in the tab order and still navigates on Enter, so a .disabled anchor looks inert but still works. Remove the href instead, because an anchor without one is neither focusable nor actionable. You can also remove the link entirely. For an element that must stay in the document and announce itself as unavailable, use aria-disabled="true", which gets the same styling.

<button type="button" class="icon-button filled" aria-label="Add" disabled>
  <span class="material-symbols" aria-hidden="true">add</span>
</button>

Copy button

Any button can copy to the clipboard. Set data-copy-for to the id of a field to copy its value, or to the id of another element to copy its trimmed text. To copy fixed text, put it in data-copy. A snackbar confirms the result, and screen readers announce it.

data-copy-message and data-copy-error replace the snackbar's texts for one button. Expressive.Copy.i18n replaces them everywhere. The root bundle starts the behavior on load. With the modular entry, call Copy.Init(). The clipboard needs HTTPS or localhost. Anywhere else, the snackbar reports the failure. Expressive.Copy.textFor(button) returns the text a button would copy, or null when its data-copy-for names no element.

<input id="api-key" value="sk-123" readonly aria-label="API key">
<button type="button" class="icon-button" data-copy-for="api-key" aria-label="Copy API key">
  <span class="material-symbols" aria-hidden="true">content_copy</span>
</button>

<button type="button" class="button text" data-copy="https://example.com/share/42"
        data-copy-message="Link copied">Copy link</button>

Tokens

Set these on the control, or on an ancestor to reach a group of them. Each size class only sets these values, so overriding them gives you a custom size.

Token Default
--md-comp-icon-button-container-height40px
--md-comp-icon-button-icon-size24px
--md-comp-icon-button-leading-space8px
--md-comp-icon-button-trailing-space8px
--md-comp-icon-button-narrow-space4px
--md-comp-icon-button-wide-space14px
--md-comp-icon-button-container-shape9999px
--md-comp-icon-button-container-shape-square12px
--md-comp-icon-button-pressed-container-shape8px
--md-comp-icon-button-outline-width1px
--md-comp-icon-button-colorvar(--md-sys-color-on-surface-variant)
--md-comp-icon-button-container-colortransparent

The state layer is always the icon colour mixed into the container at the state layer opacity, so those two colour tokens are all a new style needs.

No matching pages

  • Source color

    The seed every generated ramp derives from. Pick one and browse the docs — the whole theme follows. Error does not: it is a fixed hue.

    Default source: Mist Iris.

    ExpressiveCSS presets: Lagoon, Harbor, Breeze, Cobalt, Iris.

    Bold

    Soft

    Mist

    Glow