# Icon buttons

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

## Anatomy

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](https://github.com/BaezFJ/ExpressiveCSS/blob/master/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](/buttons.html.md).

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

[](/buttons.html.md)

```
<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](/state-layers.html.md) opacity, so those two colour tokens are all a new style needs.
