Accessibility pattern · Media

Icons

An icon next to words is decoration and is hidden; an icon that stands alone gets role=img and a name; an icon that is the whole control leaves the name to the button or link. Drawn in currentColor, every icon follows the text into dark mode and forced colors, where a fixed fill simply vanishes.

WCAG criteria
6
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.

Four jobs an icon does

  • Beside text: hidden

    Payment received

    aria-hidden="true" Heard as Download invoice, button

  • On its own: an image

    • Order 4821Out for delivery<title>
    • Order 4807aria-label

    role="img" Heard as Out for delivery, image

  • The whole button: name the button

    <button aria-label="Edit"> Heard as Edit, button

  • In a link: name the link

    <a aria-label="Call the shop"> Heard as Call the shop, link

Three ways it goes wrong

  • A title on a decorative icon

    Heard as Heart icon Save, button

  • A name on the icon and the button

    Heard as Delete Delete, button

  • A fixed fill color

    fill="#18181B" currentColor

    Switch the demo to Dark or simulate forced colors: the fixed star disappears.

Icon set

Icons shown: 24

Press an icon to copy its name.

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 through the example buttons and links, the search field and each icon in the set. Icons that are only pictures are never tab stops.
Enter or SpaceOn an icon in the set, copies its name to the clipboard and says which name was copied.
EscapeIn the search field, clears the search and shows every icon again.

Screen readers

What it announces

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

WhenExpected announcement
Focus reaches Download invoiceDownload invoice, button (the icon is silent)
Reading the first orderOrder 4821, Out for delivery, image
Focus reaches the pencilEdit, button
Focus reaches the Save button with a titled iconHeart icon Save, button
Focus reaches the Delete button with a named iconDelete Delete, button
Typing bin in the searchIcons shown: 1
An icon in the set is pressedCopied: trash

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-svg-icons" data-ap-svg-icons>
  <svg class="ap-svg-icons__sprite" aria-hidden="true" focusable="false">
    <symbol id="svgi-home" viewBox="0 0 24 24"><path d="M4 11 12 4l8 7"/><path d="M6 9.5V20h12V9.5"/><path d="M10 20v-5h4v5"/></symbol>
    <symbol id="svgi-search" viewBox="0 0 24 24"><circle cx="11" cy="11" r="6.5"/><path d="m16 16 4 4"/></symbol>
    <symbol id="svgi-user" viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"/><path d="M4.5 20c1.2-4 4-6 7.5-6s6.3 2 7.5 6"/></symbol>
    <symbol id="svgi-settings" viewBox="0 0 24 24"><path d="M4 7h9.5M18.5 7H20M4 17h3.5M12.5 17H20"/><circle cx="16" cy="7" r="2.5"/><circle cx="10" cy="17" r="2.5"/></symbol>
    <symbol id="svgi-bell" viewBox="0 0 24 24"><path d="M6 16v-5a6 6 0 0 1 12 0v5l1.5 2h-15Z"/><path d="M10 20.5a2 2 0 0 0 4 0"/></symbol>
    <symbol id="svgi-mail" viewBox="0 0 24 24"><rect x="3.5" y="5.5" width="17" height="13" rx="2"/><path d="m4 7 8 6 8-6"/></symbol>
    <symbol id="svgi-phone" viewBox="0 0 24 24"><path d="M6.5 3.5h3l1.5 4-2 1.5a11 11 0 0 0 6 6l1.5-2 4 1.5v3a2 2 0 0 1-2 2A16 16 0 0 1 4.5 5.5a2 2 0 0 1 2-2Z"/></symbol>
    <symbol id="svgi-map-pin" viewBox="0 0 24 24"><path d="M12 21s-6.5-6-6.5-11a6.5 6.5 0 0 1 13 0c0 5-6.5 11-6.5 11Z"/><circle cx="12" cy="10" r="2.5"/></symbol>
    <symbol id="svgi-calendar" viewBox="0 0 24 24"><rect x="4" y="5.5" width="16" height="15" rx="2"/><path d="M4 10h16M8.5 3.5v4M15.5 3.5v4"/></symbol>
    <symbol id="svgi-cart" viewBox="0 0 24 24"><path d="M3.5 4.5H6l2.2 10.5h10.3l1.8-7.5H7"/><circle cx="9.5" cy="19" r="1.5"/><circle cx="17" cy="19" r="1.5"/></symbol>
    <symbol id="svgi-heart" viewBox="0 0 24 24"><path d="M12 20s-7.5-4.6-7.5-10.2A4.3 4.3 0 0 1 12 7.2a4.3 4.3 0 0 1 7.5 2.6C19.5 15.4 12 20 12 20Z"/></symbol>
    <symbol id="svgi-star" viewBox="0 0 24 24"><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"/></symbol>
    <symbol id="svgi-download" viewBox="0 0 24 24"><path d="M12 4v11"/><path d="m7 10 5 5 5-5"/><path d="M5 20h14"/></symbol>
    <symbol id="svgi-upload" viewBox="0 0 24 24"><path d="M12 15V4"/><path d="m7 9 5-5 5 5"/><path d="M5 20h14"/></symbol>
    <symbol id="svgi-share" viewBox="0 0 24 24"><circle cx="18" cy="5" r="2.5"/><circle cx="6" cy="12" r="2.5"/><circle cx="18" cy="19" r="2.5"/><path d="m8.2 13.2 7.6 4.6M15.8 6.2l-7.6 4.6"/></symbol>
    <symbol id="svgi-edit" viewBox="0 0 24 24"><path d="M12 20h8"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></symbol>
    <symbol id="svgi-trash" viewBox="0 0 24 24"><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"/></symbol>
    <symbol id="svgi-check" viewBox="0 0 24 24"><path d="m5 12.5 4.5 4.5L19 7.5"/></symbol>
    <symbol id="svgi-check-circle" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="m8.5 12.5 2.5 2.5 4.5-5"/></symbol>
    <symbol id="svgi-arrow-right" viewBox="0 0 24 24"><path d="M4 12h16M14 6l6 6-6 6"/></symbol>
    <symbol id="svgi-external-link" viewBox="0 0 24 24"><path d="M14 4h6v6"/><path d="M20 4 11 13"/><path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/></symbol>
    <symbol id="svgi-info" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="M12 11v5.5M12 7.5h.01"/></symbol>
    <symbol id="svgi-lock" viewBox="0 0 24 24"><rect x="5" y="10.5" width="14" height="10" rx="2"/><path d="M8 10.5V8a4 4 0 0 1 8 0v2.5"/></symbol>
    <symbol id="svgi-truck" viewBox="0 0 24 24"><path d="M3 6.5h11v9H3zM14 9.5h4l3 3.5v2.5h-7"/><circle cx="7" cy="18.5" r="2"/><circle cx="17.5" cy="18.5" r="2"/></symbol>
  </svg>

  <section class="ap-svg-icons__part" aria-labelledby="svgi-jobs">
    <h3 class="ap-svg-icons__h" id="svgi-jobs">Four jobs an icon does</h3>
    <ul class="ap-svg-icons__cases">
      <li class="ap-svg-icons__case">
        <h4 class="ap-svg-icons__case-name">Beside text: hidden</h4>
        <div class="ap-svg-icons__show">
          <button type="button" class="ap-btn"><svg class="ap-btn__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-download"/></svg>Download invoice</button>
          <span class="ap-svg-icons__chip"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check-circle"/></svg>Payment received</span>
        </div>
        <p class="ap-svg-icons__how"><code translate="no">aria-hidden="true"</code> <span class="ap-svg-icons__heard"><span class="ap-svg-icons__k">Heard as</span> <span>Download invoice, button</span></span></p>
      </li>
      <li class="ap-svg-icons__case">
        <h4 class="ap-svg-icons__case-name">On its own: an image</h4>
        <ul class="ap-svg-icons__orders">
          <li><span>Order 4821</span><svg class="ap-svg-icons__icon ap-svg-icons__status" viewBox="0 0 24 24" role="img" aria-labelledby="svgi-transit" focusable="false"><title id="svgi-transit">Out for delivery</title><use href="#svgi-truck"/></svg><code translate="no">&lt;title&gt;</code></li>
          <li><span>Order 4807</span><svg class="ap-svg-icons__icon ap-svg-icons__status" viewBox="0 0 24 24" role="img" aria-label="Delivered" focusable="false"><use href="#svgi-check-circle"/></svg><code translate="no">aria-label</code></li>
        </ul>
        <p class="ap-svg-icons__how"><code translate="no">role="img"</code> <span class="ap-svg-icons__heard"><span class="ap-svg-icons__k">Heard as</span> <span>Out for delivery, image</span></span></p>
      </li>
      <li class="ap-svg-icons__case">
        <h4 class="ap-svg-icons__case-name">The whole button: name the button</h4>
        <div class="ap-svg-icons__show">
          <button type="button" class="ap-svg-icons__iconbtn" aria-label="Edit"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-edit"/></svg></button>
          <button type="button" class="ap-svg-icons__iconbtn" aria-label="Share"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-share"/></svg></button>
          <button type="button" class="ap-svg-icons__iconbtn" aria-label="Delete"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-trash"/></svg></button>
        </div>
        <p class="ap-svg-icons__how"><code translate="no">&lt;button aria-label="Edit"&gt;</code> <span class="ap-svg-icons__heard"><span class="ap-svg-icons__k">Heard as</span> <span>Edit, button</span></span></p>
      </li>
      <li class="ap-svg-icons__case">
        <h4 class="ap-svg-icons__case-name">In a link: name the link</h4>
        <div class="ap-svg-icons__show">
          <a class="ap-svg-icons__iconbtn" href="#svgi-jobs" aria-label="Call the shop"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-phone"/></svg></a>
          <a class="ap-svg-icons__iconbtn" href="#svgi-jobs" aria-label="Email the shop"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-mail"/></svg></a>
          <a class="ap-svg-icons__link" href="#svgi-jobs">Delivery times<svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-arrow-right"/></svg></a>
        </div>
        <p class="ap-svg-icons__how"><code translate="no">&lt;a aria-label="Call the shop"&gt;</code> <span class="ap-svg-icons__heard"><span class="ap-svg-icons__k">Heard as</span> <span>Call the shop, link</span></span></p>
      </li>
    </ul>
  </section>

  <section class="ap-svg-icons__part" aria-labelledby="svgi-wrong">
    <h3 class="ap-svg-icons__h" id="svgi-wrong">Three ways it goes wrong</h3>
    <ul class="ap-svg-icons__cases ap-svg-icons__cases--rows">
      <li class="ap-svg-icons__case ap-svg-icons__case--wrong">
        <h4 class="ap-svg-icons__case-name">A title on a decorative icon</h4>
        <div class="ap-svg-icons__show">
          <button type="button" class="ap-btn"><svg class="ap-btn__icon" viewBox="0 0 24 24" focusable="false"><title>Heart icon</title><use href="#svgi-heart"/></svg>Save</button>
        </div>
        <p class="ap-svg-icons__how"><span class="ap-svg-icons__heard"><span class="ap-svg-icons__k">Heard as</span> <span>Heart icon Save, button</span></span></p>
      </li>
      <li class="ap-svg-icons__case ap-svg-icons__case--wrong">
        <h4 class="ap-svg-icons__case-name">A name on the icon and the button</h4>
        <div class="ap-svg-icons__show">
          <button type="button" class="ap-btn"><svg class="ap-btn__icon" viewBox="0 0 24 24" aria-label="Delete" focusable="false"><use href="#svgi-trash"/></svg>Delete</button>
        </div>
        <p class="ap-svg-icons__how"><span class="ap-svg-icons__heard"><span class="ap-svg-icons__k">Heard as</span> <span>Delete Delete, button</span></span></p>
      </li>
      <li class="ap-svg-icons__case ap-svg-icons__case--wrong">
        <h4 class="ap-svg-icons__case-name">A fixed fill color</h4>
        <div class="ap-svg-icons__show ap-svg-icons__swatches">
          <span class="ap-svg-icons__swatch ap-svg-icons__swatch--fixed"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path fill="#18181B" 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><code translate="no">fill="#18181B"</code></span>
          <span class="ap-svg-icons__swatch"><svg class="ap-svg-icons__icon ap-svg-icons__icon--solid" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-star"/></svg><code translate="no">currentColor</code></span>
        </div>
        <p class="ap-svg-icons__how ap-svg-icons__how--note">Switch the demo to Dark or simulate forced colors: the fixed star disappears.</p>
      </li>
    </ul>
  </section>

  <section class="ap-svg-icons__part" aria-labelledby="svgi-set">
    <div class="ap-svg-icons__top">
      <h3 class="ap-svg-icons__h" id="svgi-set">Icon set</h3>
      <p class="ap-svg-icons__count" role="status"><span>Icons shown:</span> <span data-svgi-count>24</span></p>
    </div>
    <div class="ap-field ap-svg-icons__search">
      <label class="ap-label" for="svgi-q">Search icons</label>
      <input class="ap-input" id="svgi-q" type="search" autocomplete="off" spellcheck="false" aria-describedby="svgi-q-hint" data-svgi-search />
      <p class="ap-hint" id="svgi-q-hint">Search by name or by what it shows, like bin, basket or tick.</p>
    </div>
    <p class="ap-svg-icons__hint" id="svgi-grid-hint">Press an icon to copy its name.</p>
    <ul class="ap-svg-icons__grid" aria-label="Icons">
      <li><button type="button" class="ap-svg-icons__tile" data-svgi-name="home" data-svgi-tags="house start" aria-describedby="svgi-grid-hint"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-home"/></svg><span class="ap-svg-icons__tilename" translate="no">home</span><svg class="ap-svg-icons__done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg></button></li>
      <li><button type="button" class="ap-svg-icons__tile" data-svgi-name="search" data-svgi-tags="find magnifier look" aria-describedby="svgi-grid-hint"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-search"/></svg><span class="ap-svg-icons__tilename" translate="no">search</span><svg class="ap-svg-icons__done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg></button></li>
      <li><button type="button" class="ap-svg-icons__tile" data-svgi-name="user" data-svgi-tags="person account profile" aria-describedby="svgi-grid-hint"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-user"/></svg><span class="ap-svg-icons__tilename" translate="no">user</span><svg class="ap-svg-icons__done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg></button></li>
      <li><button type="button" class="ap-svg-icons__tile" data-svgi-name="settings" data-svgi-tags="sliders preferences options" aria-describedby="svgi-grid-hint"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-settings"/></svg><span class="ap-svg-icons__tilename" translate="no">settings</span><svg class="ap-svg-icons__done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg></button></li>
      <li><button type="button" class="ap-svg-icons__tile" data-svgi-name="bell" data-svgi-tags="notification alert reminder" aria-describedby="svgi-grid-hint"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-bell"/></svg><span class="ap-svg-icons__tilename" translate="no">bell</span><svg class="ap-svg-icons__done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg></button></li>
      <li><button type="button" class="ap-svg-icons__tile" data-svgi-name="mail" data-svgi-tags="email envelope message" aria-describedby="svgi-grid-hint"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-mail"/></svg><span class="ap-svg-icons__tilename" translate="no">mail</span><svg class="ap-svg-icons__done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg></button></li>
      <li><button type="button" class="ap-svg-icons__tile" data-svgi-name="phone" data-svgi-tags="call telephone" aria-describedby="svgi-grid-hint"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-phone"/></svg><span class="ap-svg-icons__tilename" translate="no">phone</span><svg class="ap-svg-icons__done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg></button></li>
      <li><button type="button" class="ap-svg-icons__tile" data-svgi-name="map-pin" data-svgi-tags="location place marker address" aria-describedby="svgi-grid-hint"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-map-pin"/></svg><span class="ap-svg-icons__tilename" translate="no">map-pin</span><svg class="ap-svg-icons__done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg></button></li>
      <li><button type="button" class="ap-svg-icons__tile" data-svgi-name="calendar" data-svgi-tags="date schedule booking" aria-describedby="svgi-grid-hint"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-calendar"/></svg><span class="ap-svg-icons__tilename" translate="no">calendar</span><svg class="ap-svg-icons__done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg></button></li>
      <li><button type="button" class="ap-svg-icons__tile" data-svgi-name="cart" data-svgi-tags="basket shop trolley buy" aria-describedby="svgi-grid-hint"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-cart"/></svg><span class="ap-svg-icons__tilename" translate="no">cart</span><svg class="ap-svg-icons__done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg></button></li>
      <li><button type="button" class="ap-svg-icons__tile" data-svgi-name="heart" data-svgi-tags="like love favourite favorite" aria-describedby="svgi-grid-hint"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-heart"/></svg><span class="ap-svg-icons__tilename" translate="no">heart</span><svg class="ap-svg-icons__done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg></button></li>
      <li><button type="button" class="ap-svg-icons__tile" data-svgi-name="star" data-svgi-tags="rating favourite favorite" aria-describedby="svgi-grid-hint"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-star"/></svg><span class="ap-svg-icons__tilename" translate="no">star</span><svg class="ap-svg-icons__done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg></button></li>
      <li><button type="button" class="ap-svg-icons__tile" data-svgi-name="download" data-svgi-tags="save arrow down" aria-describedby="svgi-grid-hint"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-download"/></svg><span class="ap-svg-icons__tilename" translate="no">download</span><svg class="ap-svg-icons__done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg></button></li>
      <li><button type="button" class="ap-svg-icons__tile" data-svgi-name="upload" data-svgi-tags="send arrow up" aria-describedby="svgi-grid-hint"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-upload"/></svg><span class="ap-svg-icons__tilename" translate="no">upload</span><svg class="ap-svg-icons__done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg></button></li>
      <li><button type="button" class="ap-svg-icons__tile" data-svgi-name="share" data-svgi-tags="send network" aria-describedby="svgi-grid-hint"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-share"/></svg><span class="ap-svg-icons__tilename" translate="no">share</span><svg class="ap-svg-icons__done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg></button></li>
      <li><button type="button" class="ap-svg-icons__tile" data-svgi-name="edit" data-svgi-tags="pencil write change" aria-describedby="svgi-grid-hint"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-edit"/></svg><span class="ap-svg-icons__tilename" translate="no">edit</span><svg class="ap-svg-icons__done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg></button></li>
      <li><button type="button" class="ap-svg-icons__tile" data-svgi-name="trash" data-svgi-tags="bin delete remove" aria-describedby="svgi-grid-hint"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-trash"/></svg><span class="ap-svg-icons__tilename" translate="no">trash</span><svg class="ap-svg-icons__done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg></button></li>
      <li><button type="button" class="ap-svg-icons__tile" data-svgi-name="check" data-svgi-tags="tick done yes" aria-describedby="svgi-grid-hint"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg><span class="ap-svg-icons__tilename" translate="no">check</span><svg class="ap-svg-icons__done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg></button></li>
      <li><button type="button" class="ap-svg-icons__tile" data-svgi-name="check-circle" data-svgi-tags="tick done delivered success" aria-describedby="svgi-grid-hint"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check-circle"/></svg><span class="ap-svg-icons__tilename" translate="no">check-circle</span><svg class="ap-svg-icons__done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg></button></li>
      <li><button type="button" class="ap-svg-icons__tile" data-svgi-name="arrow-right" data-svgi-tags="next forward go" aria-describedby="svgi-grid-hint"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-arrow-right"/></svg><span class="ap-svg-icons__tilename" translate="no">arrow-right</span><svg class="ap-svg-icons__done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg></button></li>
      <li><button type="button" class="ap-svg-icons__tile" data-svgi-name="external-link" data-svgi-tags="new tab open outside" aria-describedby="svgi-grid-hint"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-external-link"/></svg><span class="ap-svg-icons__tilename" translate="no">external-link</span><svg class="ap-svg-icons__done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg></button></li>
      <li><button type="button" class="ap-svg-icons__tile" data-svgi-name="info" data-svgi-tags="about help details" aria-describedby="svgi-grid-hint"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-info"/></svg><span class="ap-svg-icons__tilename" translate="no">info</span><svg class="ap-svg-icons__done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg></button></li>
      <li><button type="button" class="ap-svg-icons__tile" data-svgi-name="lock" data-svgi-tags="secure password private" aria-describedby="svgi-grid-hint"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-lock"/></svg><span class="ap-svg-icons__tilename" translate="no">lock</span><svg class="ap-svg-icons__done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg></button></li>
      <li><button type="button" class="ap-svg-icons__tile" data-svgi-name="truck" data-svgi-tags="delivery shipping van" aria-describedby="svgi-grid-hint"><svg class="ap-svg-icons__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-truck"/></svg><span class="ap-svg-icons__tilename" translate="no">truck</span><svg class="ap-svg-icons__done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#svgi-check"/></svg></button></li>
    </ul>
    <p class="ap-svg-icons__empty" data-svgi-empty hidden>No icon matches that. Try a shorter word.</p>
    <p class="ap-svg-icons__copied" role="status" data-svgi-copied></p>
  </section>
</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

    Decorative icons are aria-hidden, standalone icons are images with a name, and an icon-only control is named on the control itself.

  • 1.4.11 Non-text Contrast Level AA

    Icons are drawn in currentColor at the text's own contrast, and keep it in dark mode and forced colors.

  • 2.4.4 Link Purpose (In Context) Level A

    Icon-only links carry their purpose in aria-label, so a phone glyph is announced as Call the shop.

  • 2.5.3 Label in Name Level A

    Where an icon sits beside a visible word, the word is the whole name, so speech users can say what they see.

  • 4.1.2 Name, Role, Value Level A

    Every button and link has exactly one name, with no icon text added to it or doubled inside it.

  • 4.1.3 Status Messages Level AA

    The number of matching icons and the name just copied are announced from status messages, without moving focus.

Usage

When to use it

Use it

  • Icons that support a word, like a download arrow on a Download button: hide them.
  • Icons that carry information nothing else shows, like an order's status: make them images with a name.
  • Toolbars and dense rows where a familiar icon is the whole control: name the control.

Use something else

  • An icon alone for an action people may not recognise: add a visible word.
  • Icon fonts: their glyphs can be read as odd characters and vanish when people pick their own fonts.
  • Icons as CSS background images for anything meaningful: they have no name and disappear in forced colors.

Common failures

How it usually goes wrong

  • A title on a decorative icon

    A <title> inside an icon that sits beside text is read with the text: "Heart icon Save". Decorative icons get aria-hidden and no title.

  • A name on the icon inside a named button

    aria-label on the SVG and a word in the button gives "Delete Delete". Put the name on the button once and hide the icon.

  • Fixed fill colors

    A fill written into the SVG ignores the theme: it disappears on a dark background and stays put in forced colors. currentColor follows the text.

  • A standalone icon with no role

    Without role=img, screen readers may skip an SVG's title or read it as a group. role=img makes it one named image.

  • Icons as tab stops

    Old browsers put SVGs in the Tab order. focusable="false" keeps them out, so keyboard users stop only on controls.

Notes

Building it

  • For a standalone icon, role=img with aria-label is the simplest; role=img with a <title> referenced by aria-labelledby reaches the most screen readers and also gives a hover tooltip. Both are shown above.
  • In a sprite, put aria-hidden on the sprite and decide hidden or named at each <use>, never on the <symbol>: the symbol is not what gets announced.
  • Draw with stroke or fill set to currentColor in CSS. Chromium's forced colors mode changes the text color but not SVG fills, so only currentColor icons follow it.
  • The forced-colors option is a simulation built from CSS system colors (Canvas, CanvasText, LinkText, Highlight). Check the real thing with a Windows contrast theme or the forced-colors emulation in browser developer tools.
  • The icon names in the set are code, marked translate="no", so they stay the same in every language.

Sources: W3C WAI: Decorative images · SVG Accessibility API Mappings · MDN: forced-colors

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