# Icon-only buttons with no accessible name

Source: https://easeweb.dev/learn/icon-buttons
Topics: Buttons and links > Icon-only buttons have a name (WCAG 4.1.2, 1.1.1)
The fix: Give each icon button a name in words and hide the icon from assistive technology. Either put visually hidden text inside the button, or add an aria-label to it, or show a visible text label next to the icon. All three pass; hidden text and aria-label keep the design, and a visible label helps everyone.
Test with: keyboard, screen reader, zoom, forced colors
References: https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html https://www.w3.org/WAI/WCAG22/Techniques/aria/ARIA14 https://www.w3.org/WAI/WCAG22/Techniques/failures/F59 https://www.w3.org/WAI/ARIA/apg/practices/names-and-descriptions/

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

## What happens

A stationery shop's header has the shop's name on the left and three round buttons on the right: a magnifying glass for search, a bag with a small "2" badge for the basket, and three lines for the menu. Sighted mouse users recognise the icons and click them. A screen reader user who presses Tab hears "button", then "2, button", then "button". Nothing tells them which one opens the menu.

## Why it fails

Each button contains only an inline SVG drawn with a `use` reference to a sprite. An SVG with no `title` and no `aria-label` contributes no text, so the button has no accessible name. The only text inside is the badge's "2". WCAG 4.1.2 needs every control to have a name that can be determined by software, and 1.1.1 needs the icon's meaning to be available as text.

## Who is affected

Screen reader users hear a role with no purpose and have to press each button to find out what it does. In a list of form controls, the three entries are indistinguishable. Voice control users say a button's name to press it; with no name they fall back to slow numbered overlays. People who don't recognise an icon, such as three lines for a menu, also gain from words.

## What automation and AI miss

axe's `button-name` reliably flags a button with no name. It does not judge the name, so it passes `aria-label="icon"`, `aria-label="magnifier"` or a file name copied from the sprite. A quick AI fix often names the picture instead of the action, adds `aria-label` to the SVG but leaves the button empty, or names the basket "Basket" while the visible badge adds a stray "2". Whether the name makes sense is something only a person can check.

## Before: icons with no words

```html
<header class="site-header">
  <a href="/" class="logo">Fern & Fold</a>
  <button type="button" class="icon-button">
    <svg class="icon"><use href="#icon-search"></use></svg>
  </button>
  <button type="button" class="icon-button">
    <svg class="icon"><use href="#icon-bag"></use></svg>
    <span class="badge">2</span>
  </button>
  <button type="button" class="icon-button">
    <svg class="icon"><use href="#icon-menu"></use></svg>
  </button>
</header>
```

The SVGs give the buttons no text, so the search and menu buttons have no name and the basket is named "2". All three are announced as buttons and nothing more.

## After: visually hidden text inside the button

```html
<button type="button" class="icon-button">
  <svg class="icon" aria-hidden="true"><use href="#icon-search"></use></svg>
  <span class="visually-hidden">Search</span>
</button>
<button type="button" class="icon-button">
  <svg class="icon" aria-hidden="true"><use href="#icon-bag"></use></svg>
  <span class="badge" aria-hidden="true">2</span>
  <span class="visually-hidden">Basket, 2 items</span>
</button>
<button type="button" class="icon-button">
  <svg class="icon" aria-hidden="true"><use href="#icon-menu"></use></svg>
  <span class="visually-hidden">Menu</span>
</button>
```

```css
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
```

The words are real text in the page, so the buttons are announced as "Search, button", "Basket, 2 items, button" and "Menu, button", and browser translation translates them. The badge is hidden because the hidden text already includes the count; without that, the count would be read twice.

## After: an aria-label on the button

```html
<button type="button" class="icon-button" aria-label="Search">
  <svg class="icon" aria-hidden="true"><use href="#icon-search"></use></svg>
</button>
<button type="button" class="icon-button" aria-label="Basket, 2 items">
  <svg class="icon" aria-hidden="true"><use href="#icon-bag"></use></svg>
  <span class="badge" aria-hidden="true">2</span>
</button>
<button type="button" class="icon-button" aria-label="Menu">
  <svg class="icon" aria-hidden="true"><use href="#icon-menu"></use></svg>
</button>
```

`aria-label` replaces everything inside the button as its name, so the announcements are the same as with hidden text. It needs no extra CSS and is the usual prop on an icon-button component. Put it on the `button`, not on the `svg`, and update it when the count changes.

## After: a visible text label

```html
<button type="button" class="icon-button">
  <svg class="icon" aria-hidden="true"><use href="#icon-search"></use></svg>
  <span class="label">Search</span>
</button>
<button type="button" class="icon-button">
  <svg class="icon" aria-hidden="true"><use href="#icon-bag"></use></svg>
  <span class="label">Basket</span>
  <span class="badge">2<span class="visually-hidden"> items</span></span>
</button>
<button type="button" class="icon-button">
  <svg class="icon" aria-hidden="true"><use href="#icon-menu"></use></svg>
  <span class="label">Menu</span>
</button>
```

The word under each icon is the button's name, so everyone gets the same label: screen reader users hear "Basket 2 items, button", voice control users say what they see, and people who don't know the icon can read it.

## Which option to use

All three give each button a name and meet 4.1.2 and 1.1.1. Hidden text keeps the design unchanged and is translated with the page, so it suits a header with no room for words. `aria-label` keeps the design too and is the simplest in a component, but some browser translation tools leave it in the original language. A visible label costs space, but it is the only option that also helps people who don't recognise the icon. Choose it where the layout allows, and use one of the other two where it doesn't.

## Implementation decisions

**Name the action, not the picture.** "Search", "Close" and "Menu" describe what happens. "Magnifier", "X" and "Hamburger" describe the drawing, and a file name such as "icon-bag.svg" describes nothing. A toggle such as a favourite heart keeps one name and exposes its state with `aria-pressed`, rather than swapping the name.

**Hide the icon, every time.** Without `aria-hidden="true"`, some screen readers announce an unnamed SVG as "image" or "group", and icon fonts can read out a private-use character. An SVG with its own `title` inside a named button is read twice in some combinations.

**Tooltips are not names.** A `title` attribute or a tooltip that appears on hover may be used as a fallback name, but not consistently, and it never appears for touch or keyboard users in every browser. Name the button directly, and keep any tooltip text the same as the name.

**Components.** Make the name a required prop of the shared icon-button component, so an unnamed button can't be built. If the prop is optional, it will be left out.

**Counts and changing state.** When the basket count changes, update the name as well as the badge. If people need to hear the change, announce it in a status region; changing a button's name alone is not announced.

## Verify the fix

1. Tab to each icon button and check, in the browser's accessibility tree, that its name says what it does and that the icon contributes nothing.
2. With a screen reader, move through the header and check each button is announced as its name followed by "button", with the basket count read once.
3. With voice control, say each button's name, such as "click Search", and check it activates that button.
4. Translate the page with the browser and check the names change language too; then check the icons at 200% zoom and in forced colors.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the shared icon-button component changes.

## Limits

This is a starting pattern, not a guarantee for every application. The sprite ids and class names in the snippets are placeholders. Icon buttons added later by content editors or third-party widgets need the same check. Test the complete journey with your target assistive technology.
