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.
| Style | Container | Icon | Use it for |
|---|---|---|---|
| standard (default) | none | on-surface-variant | The usual case, in toolbars, list rows and 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
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>
As a link
An icon button that navigates is an <a>. It
takes icon-button alone. Do not add button,
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>
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-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.