Icon buttons

Icon buttons

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

An icon button is one action expressed as one icon — the toolbar overflow, the close on a dialog, 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.

The icon is decoration, so it is aria-hidden — which leaves the control with nothing to be named by. Every icon button needs an aria-label. That is not a suggestion here: SEMANTICS.md enforces both halves against the docs, and a bare icon button fails the suite.

It 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, a footer. Those hosts style the plain controls they are given and step aside for a control that styles itself, the same way they already do for button. A navigation drawer row is the exception: every row there is one 48dp shape, .button included.

This is a component in its own right, not a modifier on the common button. Material 3 gives it its own token families, its own size ladder 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

Four styles, and the modifier names are the button axis's: 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; text is what standard already is.

StyleContainerIconUse it for
standard (default)noneon-surface-variantThe usual case — toolbars, list rows, 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

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

Width is the inset plus the icon, not a size of its own — which is why the default width of every size equals its height, and why a round icon button is a circle without being told to be one. Add narrow to tighten the inset or wide to open it up; both follow whichever size they are on.

<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

Round by default. Add square for the squared container, whose corner grows with the size — 12dp on the two small sizes, 16dp on medium, 28dp on the two large ones. Either way the corner tightens while the button is pressed, which is the Material 3 Expressive shape morph.

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

Disabled

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 stops the pointer and nothing else: the link stays in the tab order and still navigates on Enter, so a .disabled anchor is a control that looks inert and is not. Drop the href — an anchor without one is neither focusable nor actionable — or drop the link entirely. aria-disabled="true" is styled the same way for the case where the element must stay in the document and announce itself as unavailable.

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

Tokens

Set these on the control, or on an ancestor to reach a group of them. The size classes are nothing but a block of these values, so an override is a size of your own.

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.

  • 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.