Accessibility pattern · Buttons and actions

Icon button

An icon is not a name: each button here gets one with aria-label, and a tooltip shows the same word to everyone who can see it. The favorite button keeps its name while aria-pressed reports whether it is on.

WCAG criteria
7
Keyboard rules
3
Checked with
axe, keyboard and the inspector

Live demo

Try it

Use it with a mouse, a keyboard or a screen reader. The inspector beside it shows what the browser tells assistive technology as you go: focus, state changes and announcements.

Goa trip budget

Spreadsheet · Edited today by Ananya

Flights, stays and food for four people, 12 to 16 December.

The demo works without JavaScript only as far as its HTML does; the inspector needs JavaScript.

Keyboard

Keys it answers to

Every action works without a pointer.

KeyWhat it does
Tab or ShiftTabMoves from one icon button to the next; the focused button's tooltip appears at once.
Enter or SpacePresses the focused button. On Favorite, switches it on or off; on More actions, shows or hides two more actions.
EscapeHides the tooltip without moving focus. It comes back when focus or the pointer moves to another button.

Screen readers

What it announces

Written from the roles, names and states in the markup.

WhenExpected announcement
Focus reaches the pencil iconEdit, button
Focus reaches the starFavorite, toggle button, not pressed
Space presses itPressed (the name stays Favorite)
More actions is pressedMore actions, button, expanded
Delete is pressedMoved to bin

These are expected announcements, not recordings. Wording and order differ between screen readers and browsers.

Code

Copy the code

The exact files this demo runs on. The styles are served with a prefix that keeps this site's own styles out of the demo; what you copy is the original.

The markup

<div class="ap-icon-button" data-ap-icon-button>
  <div class="ap-icon-button__card">
    <div class="ap-icon-button__top">
      <span class="ap-icon-button__file" aria-hidden="true">
        <svg viewBox="0 0 24 24" focusable="false"><rect x="4" y="3" width="16" height="18" rx="2"/><path d="M4 9h16M4 15h16M10 3v18"/></svg>
      </span>
      <div class="ap-icon-button__about">
        <h3 class="ap-icon-button__name">Goa trip budget</h3>
        <p class="ap-icon-button__meta">Spreadsheet · Edited today by Ananya</p>
      </div>
      <button type="button" class="ap-icon-button__btn ap-icon-button__btn--fav" aria-label="Favorite" aria-pressed="false" data-ap-tip>
        <svg class="ap-icon-button__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m12 3.5 2.6 5.3 5.9.9-4.3 4.1 1 5.8-5.2-2.7-5.2 2.7 1-5.8-4.3-4.1 5.9-.9Z"/></svg>
      </button>
    </div>
    <p class="ap-icon-button__desc">Flights, stays and food for four people, 12 to 16 December.</p>

    <div class="ap-icon-button__bar">
      <button type="button" class="ap-icon-button__btn" aria-label="Edit" data-ap-tip data-ap-action="edit">
        <svg class="ap-icon-button__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>
      </button>
      <button type="button" class="ap-icon-button__btn" aria-label="Share" data-ap-tip data-ap-action="share">
        <svg class="ap-icon-button__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><path d="m8.6 13.5 6.8 4M15.4 6.5l-6.8 4"/></svg>
      </button>
      <button type="button" class="ap-icon-button__btn ap-icon-button__btn--danger" aria-label="Delete" data-ap-tip data-ap-action="delete">
        <svg class="ap-icon-button__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 7h16"/><path d="M10 11v6M14 11v6"/><path d="m6 7 1 12a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-12"/><path d="M9 7V4h6v3"/></svg>
      </button>
      <button type="button" class="ap-icon-button__btn ap-icon-button__btn--more" aria-label="More actions" aria-expanded="false" aria-controls="icon-button-more" data-ap-tip>
        <svg class="ap-icon-button__icon ap-icon-button__icon--solid" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="5" cy="12" r="1.75"/><circle cx="12" cy="12" r="1.75"/><circle cx="19" cy="12" r="1.75"/></svg>
      </button>
    </div>

    <div class="ap-icon-button__more" id="icon-button-more" hidden>
      <button type="button" class="ap-btn ap-btn--ghost" data-ap-action="download">
        <svg class="ap-btn__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 4v11"/><path d="m7 10 5 5 5-5"/><path d="M5 20h14"/></svg>
        Download
      </button>
      <button type="button" class="ap-btn ap-btn--ghost" data-ap-action="duplicate">
        <svg class="ap-btn__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg>
        Duplicate
      </button>
    </div>

    <p class="ap-icon-button__status" role="status"></p>
  </div>
  <span class="ap-icon-button__tip" aria-hidden="true" hidden></span>
</div>

WCAG 2.2

What it meets

The success criteria this pattern takes care of, and how.

  • 1.1.1 Non-text Content Level A

    Each icon is hidden from assistive technology and its button carries the text alternative in aria-label.

  • 1.4.11 Non-text Contrast Level AA

    Icons are drawn at 3:1 or more against the card, and the pressed star is filled, not just tinted.

  • 1.4.13 Content on Hover or Focus Level AA

    The tooltip can be dismissed with Escape, the pointer can move onto it, and it stays until hover or focus moves away.

  • 2.4.7 Focus Visible Level AA

    Every button shows a two-pixel focus ring around its whole 44-pixel square.

  • 2.5.8 Target Size (Minimum) Level AA

    Each button is 44 by 44 pixels with the 20-pixel icon centred, well above the 24-pixel minimum.

  • 4.1.2 Name, Role, Value Level A

    Names come from aria-label, the favorite state from aria-pressed, and More actions reports expanded or collapsed.

  • 4.1.3 Status Messages Level AA

    The result of Edit, Share, Delete and the extra actions is announced in a status message.

Usage

When to use it

Use it

  • Actions repeated on every row or card, where a familiar icon saves space: edit, share, delete.
  • Toolbars where the icons are widely known and a tooltip is always there to confirm them.

Use something else

  • Actions whose icon is not universally understood: add a visible text label instead.
  • The main action on a screen: give it a full text button.
  • Anything where a wrong guess is costly and cannot be undone.

Common failures

How it usually goes wrong

  • No accessible name

    An icon-only button with no label is announced as just "button". aria-label gives each one a name, in words people would say to press it by voice.

  • A title attribute as the only label

    title appears only on mouse hover after a delay, never on keyboard focus or touch, and screen readers treat it inconsistently. The name here is aria-label, and the tooltip also appears on focus.

  • A name that flips with the state

    Changing "Favorite" to "Unfavorite" while also setting aria-pressed gives "Unfavorite, pressed", a double negative. The name stays; aria-pressed carries the state.

  • Tooltips that will not go away

    A tooltip that covers its neighbours and only goes when the pointer leaves gets in the way of people who magnify the screen. Escape hides it here, and the pointer can rest on it.

  • Targets the size of the icon

    A 16-pixel icon with no padding is hard to hit with a finger or with a tremor. Each button is a 44-pixel square, with the icon in the middle.

  • The icon announced instead of the button

    An SVG without aria-hidden can be read as an image, or by its path data in older tools. The icon is hidden and the button holds the name.

Notes

Building it

  • Keep aria-label to the word people would say: speech-input users press it by saying "click Share".
  • The tooltip is aria-hidden because it repeats the name exactly. If a tooltip ever adds information, give it role=tooltip and point aria-describedby at it instead.
  • The tooltip opens after a short pause on hover and at once on keyboard focus. It never opens on touch, where there is no hover to end it.
  • aria-pressed suits a setting that stays on or off. More actions shows a section instead, so it uses aria-expanded.

Sources: WAI-ARIA Authoring Practices: Button · WAI-ARIA Authoring Practices: Tooltip · Understanding WCAG 2.2: Content on Hover or Focus

Checked with axe in light and dark themes, at desktop and phone widths, and by keyboard. Not yet tested with every screen reader and browser pair. Report a correction