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.
| Style | Container | Icon | Use it for |
|---|---|---|---|
| standard (default) | none | on-surface-variant | The usual case — toolbars, list rows, app bars. |
filled | primary | on-primary | The one high-emphasis action on the screen. |
tonal | secondary-container | on-secondary-container | Medium emphasis, without the weight of a filled one. |
outlined | none, outline-variant border | on-surface-variant | Medium 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>
As a link
An icon button that navigates is an <a>. Note it
takes icon-button alone — not button as
well, which is the common button's class.
<a class="icon-button outlined" href="/buttons" aria-label="Open the Buttons page">
<span class="material-symbols" aria-hidden="true">open_in_new</span>
</a>
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-height | 40px |
--md-comp-icon-button-icon-size | 24px |
--md-comp-icon-button-leading-space | 8px |
--md-comp-icon-button-trailing-space | 8px |
--md-comp-icon-button-narrow-space | 4px |
--md-comp-icon-button-wide-space | 14px |
--md-comp-icon-button-container-shape | 9999px |
--md-comp-icon-button-container-shape-square | 12px |
--md-comp-icon-button-pressed-container-shape | 8px |
--md-comp-icon-button-outline-width | 1px |
--md-comp-icon-button-color | var(--md-sys-color-on-surface-variant) |
--md-comp-icon-button-container-color | transparent |
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.