Accessibility pattern · Media

Image alternatives

Alt text replaces the picture for people who cannot see it, so it depends on the job the picture does: describe it, skip it, name its destination or repeat its words. Here the pictures are inline SVG, so role="img" with aria-label does the work of alt, and aria-hidden marks the decoration.

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

One picture, four jobs

Each image needs a different kind of alternative. Try the wrong one to hear what changes.

  • Informative image

    A product photo in a shop

    Blue pottery teapot

    ₹2,400

    alt="White teapot hand-painted with cobalt-blue flowers"

    Screen reader says

    White teapot hand-painted with cobalt-blue flowers, image

    Right. Says what a shopper needs to see; the name is already in the text below.

  • Decorative image

    A divider between two paragraphs

    Our story

    Every piece is thrown, glazed and painted by hand in our Jaipur workshop.

    Each batch is fired twice, so the cobalt stays bright.

    alt=""

    Screen reader says

    Nothing: the divider is skipped.

    Right. Pure decoration. aria-hidden on an SVG, or alt="" on an img, keeps it out of the way.

  • Functional image

    A logo that links to the home page

    Free delivery over ₹999

    alt="Neel Pottery home"

    Screen reader says

    Neel Pottery home, link

    Right. A linked image is named for where it goes, not for how it looks.

  • Image of text

    A sale banner

    DIWALI SALE 30% off all lamps Until 2 November

    alt="Diwali sale: 30% off all lamps, until 2 November"

    Screen reader says

    Diwali sale: 30% off all lamps, until 2 November, image

    Right. Repeats the words in the picture, because they are the point of it.

Which alt does my image need?

Answer up to four questions; the advice appears below them.

  1. Is the image the only thing inside a link or a button?

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
TabMoves to the logo link, each Try the wrong alt button, then the questions of the decision tree.
Enter or SpaceOn Try the wrong alt, swaps that picture's alt for the wrong one and back, and shows what a screen reader says.
Arrow Down or Arrow UpIn a question, chooses Yes or No; the next question or the advice appears below it.
Enter or SpaceOn Start again, clears every answer and puts focus back on the first question.

Screen readers

What it announces

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

WhenExpected announcement
Reading reaches the product photoWhite teapot hand-painted with cobalt-blue flowers, image
Reading reaches the dividerNothing: it is skipped
Focus reaches the logo linkNeel Pottery home, link
Try the wrong alt on the product photoTry the wrong alt, toggle button, pressed. Screen reader says: teapot-final-v2.jpg, image. Wrong. With no alt at all, many screen readers read out the file name.
Focus reaches the logo link with the wrong altLink
The last answer gives the adviceAdvice: Describe what matters, in a sentence. Say what the image shows that matters here…

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-alt" data-ap-image-alt>
  <div class="ap-alt__intro">
    <h3 class="ap-alt__name">One picture, four jobs</h3>
    <p class="ap-alt__lead">Each image needs a different kind of alternative. Try the wrong one to hear what changes.</p>
  </div>

  <ul class="ap-alt__cases">
    <li class="ap-alt__case" data-alt-case>
      <div class="ap-alt__case-top">
        <h4 class="ap-alt__kind"><span class="ap-alt__num" aria-hidden="true">1</span>Informative image</h4>
        <p class="ap-alt__ctx">A product photo in a shop</p>
      </div>
      <div class="ap-alt__scene">
        <div class="ap-alt__product">
        <svg class="ap-alt__pic" viewBox="0 0 320 190" preserveAspectRatio="xMidYMid slice" role="img" aria-label="White teapot hand-painted with cobalt-blue flowers" data-alt-img data-wrong-label="teapot-final-v2.jpg">
          <defs>
            <linearGradient id="ialt-wall" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#F5EBDD"/><stop offset="1" stop-color="#E8D4BD"/></linearGradient>
            <linearGradient id="ialt-glaze" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#FFFFFF"/><stop offset="1" stop-color="#D8E1EC"/></linearGradient>
            <g id="ialt-flower"><g fill="#1D4ED8"><ellipse cy="-6" rx="4.6" ry="6"/><ellipse cy="-6" rx="4.6" ry="6" transform="rotate(72)"/><ellipse cy="-6" rx="4.6" ry="6" transform="rotate(144)"/><ellipse cy="-6" rx="4.6" ry="6" transform="rotate(216)"/><ellipse cy="-6" rx="4.6" ry="6" transform="rotate(288)"/></g><circle r="3.4" fill="#F8FAFC"/><circle r="1.8" fill="#0E7490"/></g>
          </defs>
          <rect width="320" height="190" fill="url(#ialt-wall)"/>
          <rect y="150" width="320" height="40" fill="#D6BE9F"/>
          <ellipse cx="164" cy="160" rx="96" ry="9" fill="#4A3426" opacity=".2"/>
          <g transform="translate(160 106) scale(1.18) translate(-160 -106)">
          <path d="M106 92c-32-6-40 48-2 52" fill="none" stroke="#C3CEDB" stroke-width="13" stroke-linecap="round"/>
          <path d="M106 92c-32-6-40 48-2 52" fill="none" stroke="#F8FAFC" stroke-width="8" stroke-linecap="round"/>
          <path d="M206 114c18-2 30-18 40-42l9 4c-8 30-22 50-44 56z" fill="url(#ialt-glaze)" stroke="#C3CEDB" stroke-width="1.5"/>
          <path d="M128 152h66l-7 8h-52z" fill="#C3CEDB"/>
          <path d="M102 112c0-34 26-50 58-50s58 16 58 50c0 26-18 42-58 42s-58-16-58-42z" fill="url(#ialt-glaze)" stroke="#C3CEDB" stroke-width="1.5"/>
          <path d="M107 98c34 11 72 11 106 0" fill="none" stroke="#1E40AF" stroke-width="3"/>
          <path d="M112 138c30 9 66 9 96 0" fill="none" stroke="#1E40AF" stroke-width="2.5"/>
          <path d="M146 118c-8-6-16-4-20 2 8 2 14 2 20-2zM176 112c8-6 16-4 20 2-8 2-14 2-20-2z" fill="#0F766E"/>
          <use href="#ialt-flower" transform="translate(160 119)"/>
          <use href="#ialt-flower" transform="translate(129 122) scale(1.05)"/>
          <use href="#ialt-flower" transform="translate(191 121) scale(.9)"/>
          <ellipse cx="160" cy="64" rx="34" ry="8" fill="#E2E8F0" stroke="#C3CEDB"/>
          <path d="M137 62c5-11 41-11 46 0" fill="#F8FAFC"/>
          <circle cx="160" cy="52" r="7" fill="#1E40AF"/>
          <path d="M120 96c5-14 16-21 28-24" fill="none" stroke="#FFFFFF" stroke-width="5" stroke-linecap="round" opacity=".85"/>
          </g>
        </svg>
        <div class="ap-alt__prodline">
          <p class="ap-alt__prodname">Blue pottery teapot</p>
          <p class="ap-alt__price">₹2,400</p>
        </div>
      </div>
      </div>
      <p class="ap-alt__code"><code data-alt-right>alt="White teapot hand-painted with cobalt-blue flowers"</code><code data-alt-wrong hidden>&lt;img src="teapot-final-v2.jpg"&gt;</code></p>
      <div class="ap-alt__hear">
        <p class="ap-alt__say-k"><svg class="ap-alt__say-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M11 5 6 9H3v6h3l5 4V5Z"/><path d="M15.5 8.5a5 5 0 0 1 0 7M18.5 5.5a9 9 0 0 1 0 13"/></svg><span>Screen reader says</span></p>
        <p class="ap-alt__speech" data-alt-right>White teapot hand-painted with cobalt-blue flowers, image</p>
        <p class="ap-alt__speech" data-alt-wrong hidden>teapot-final-v2.jpg, image</p>
        <p class="ap-alt__why ap-alt__why--right" data-alt-right><svg class="ap-alt__mark" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12 5 5L20 7"/></svg><span><strong>Right.</strong> Says what a shopper needs to see; the name is already in the text below.</span></p>
        <p class="ap-alt__why ap-alt__why--wrong" data-alt-wrong hidden><svg class="ap-alt__mark" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6 6l12 12M18 6 6 18"/></svg><span><strong>Wrong.</strong> With no alt at all, many screen readers read out the file name.</span></p>
      </div>
      <button type="button" class="ap-btn ap-alt__try" aria-pressed="false" data-alt-try>
        <svg class="ap-btn__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 12a8 8 0 0 1 14-5.3L20 9"/><path d="M20 4v5h-5"/><path d="M20 12a8 8 0 0 1-14 5.3L4 15"/><path d="M4 20v-5h5"/></svg>
        Try the wrong alt
      </button>
    </li>

    <li class="ap-alt__case" data-alt-case>
      <div class="ap-alt__case-top">
        <h4 class="ap-alt__kind"><span class="ap-alt__num" aria-hidden="true">2</span>Decorative image</h4>
        <p class="ap-alt__ctx">A divider between two paragraphs</p>
      </div>
      <div class="ap-alt__scene">
        <div class="ap-alt__article">
          <p class="ap-alt__kicker">Our story</p>
          <p>Every piece is thrown, glazed and painted by hand in our Jaipur workshop.</p>
          <svg class="ap-alt__flourish" viewBox="0 0 240 28" aria-hidden="true" focusable="false" data-alt-img data-wrong-label="Image of a decorative divider">
            <path d="M6 14h74M160 14h74"/>
            <path d="M80 14c10-13 26-13 28 0-2 9-13 9-15 2M160 14c-10-13-26-13-28 0 2 9 13 9 15 2"/>
            <path class="ap-alt__flourish-fill" d="m120 4 9 10-9 10-9-10z"/>
            <circle class="ap-alt__flourish-fill" cx="100" cy="23" r="1.8"/>
            <circle class="ap-alt__flourish-fill" cx="140" cy="23" r="1.8"/>
          </svg>
          <p>Each batch is fired twice, so the cobalt stays bright.</p>
        </div>
      </div>
      <p class="ap-alt__code"><code data-alt-right>alt=""</code><code data-alt-wrong hidden>alt="Image of a decorative divider"</code></p>
      <div class="ap-alt__hear">
        <p class="ap-alt__say-k"><svg class="ap-alt__say-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M11 5 6 9H3v6h3l5 4V5Z"/><path d="M15.5 8.5a5 5 0 0 1 0 7M18.5 5.5a9 9 0 0 1 0 13"/></svg><span>Screen reader says</span></p>
        <p class="ap-alt__speech" data-alt-right>Nothing: the divider is skipped.</p>
        <p class="ap-alt__speech" data-alt-wrong hidden>Image of a decorative divider, image</p>
        <p class="ap-alt__why ap-alt__why--right" data-alt-right><svg class="ap-alt__mark" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12 5 5L20 7"/></svg><span><strong>Right.</strong> Pure decoration. aria-hidden on an SVG, or alt="" on an img, keeps it out of the way.</span></p>
        <p class="ap-alt__why ap-alt__why--wrong" data-alt-wrong hidden><svg class="ap-alt__mark" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6 6l12 12M18 6 6 18"/></svg><span><strong>Wrong.</strong> Decoration now interrupts the story, and "image" is said twice.</span></p>
      </div>
      <button type="button" class="ap-btn ap-alt__try" aria-pressed="false" data-alt-try>
        <svg class="ap-btn__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 12a8 8 0 0 1 14-5.3L20 9"/><path d="M20 4v5h-5"/><path d="M20 12a8 8 0 0 1-14 5.3L4 15"/><path d="M4 20v-5h5"/></svg>
        Try the wrong alt
      </button>
    </li>

    <li class="ap-alt__case" data-alt-case>
      <div class="ap-alt__case-top">
        <h4 class="ap-alt__kind"><span class="ap-alt__num" aria-hidden="true">3</span>Functional image</h4>
        <p class="ap-alt__ctx">A logo that links to the home page</p>
      </div>
      <div class="ap-alt__scene">
        <div class="ap-alt__site">
          <div class="ap-alt__chrome" aria-hidden="true"><span></span><span></span><span></span></div>
          <div class="ap-alt__header">
            <a class="ap-alt__logo" href="#ialt-home">
              <svg class="ap-alt__logo-art" viewBox="0 0 172 36" role="img" aria-label="Neel Pottery home" data-alt-img data-wrong-label="">
                <path class="ap-alt__logo-pot" d="M6 13h24l-2.6 13.4A7 7 0 0 1 20.5 32h-5a7 7 0 0 1-6.9-5.6Z"/>
                <path class="ap-alt__logo-rim" d="M4 9.5h28v4H4z"/>
                <path class="ap-alt__logo-leaf" d="M18 4c4 0 6 2 6 5.5-4 0-6-2-6-5.5Z"/>
                <text class="ap-alt__logo-word" x="42" y="24.5">Neel Pottery</text>
              </svg>
            </a>
            <p class="ap-alt__deal">Free delivery over ₹999</p>
          </div>
          <div class="ap-alt__skel" aria-hidden="true"><span></span><span></span><span></span></div>
        </div>
      </div>
      <p class="ap-alt__code"><code data-alt-right>alt="Neel Pottery home"</code><code data-alt-wrong hidden>alt=""</code></p>
      <div class="ap-alt__hear">
        <p class="ap-alt__say-k"><svg class="ap-alt__say-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M11 5 6 9H3v6h3l5 4V5Z"/><path d="M15.5 8.5a5 5 0 0 1 0 7M18.5 5.5a9 9 0 0 1 0 13"/></svg><span>Screen reader says</span></p>
        <p class="ap-alt__speech" data-alt-right>Neel Pottery home, link</p>
        <p class="ap-alt__speech" data-alt-wrong hidden>Link</p>
        <p class="ap-alt__why ap-alt__why--right" data-alt-right><svg class="ap-alt__mark" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12 5 5L20 7"/></svg><span><strong>Right.</strong> A linked image is named for where it goes, not for how it looks.</span></p>
        <p class="ap-alt__why ap-alt__why--wrong" data-alt-wrong hidden><svg class="ap-alt__mark" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6 6l12 12M18 6 6 18"/></svg><span><strong>Wrong.</strong> The link has no name, so people hear only "link", or the web address instead.</span></p>
      </div>
      <button type="button" class="ap-btn ap-alt__try" aria-pressed="false" data-alt-try>
        <svg class="ap-btn__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 12a8 8 0 0 1 14-5.3L20 9"/><path d="M20 4v5h-5"/><path d="M20 12a8 8 0 0 1-14 5.3L4 15"/><path d="M4 20v-5h5"/></svg>
        Try the wrong alt
      </button>
    </li>

    <li class="ap-alt__case" data-alt-case>
      <div class="ap-alt__case-top">
        <h4 class="ap-alt__kind"><span class="ap-alt__num" aria-hidden="true">4</span>Image of text</h4>
        <p class="ap-alt__ctx">A sale banner</p>
      </div>
      <div class="ap-alt__scene">
        <div class="ap-alt__banner">
        <svg class="ap-alt__pic" viewBox="0 0 320 190" role="img" aria-label="Diwali sale: 30% off all lamps, until 2 November" data-alt-img data-wrong-label="Sale banner">
          <defs>
            <linearGradient id="ialt-night" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#2E2A78"/><stop offset="1" stop-color="#6D28D9"/></linearGradient>
            <radialGradient id="ialt-glow"><stop offset="0" stop-color="#FDE68A" stop-opacity=".85"/><stop offset="1" stop-color="#FDE68A" stop-opacity="0"/></radialGradient>
          </defs>
          <rect width="320" height="190" fill="url(#ialt-night)"/>
          <g fill="#E0E7FF" opacity=".7"><circle cx="196" cy="26" r="1.6"/><circle cx="292" cy="40" r="1.2"/><circle cx="226" cy="58" r="1"/><circle cx="300" cy="150" r="1.4"/><circle cx="182" cy="164" r="1"/></g>
          <circle cx="254" cy="104" r="62" fill="url(#ialt-glow)"/>
          <path d="M219 126c4 24 66 24 70 0z" fill="#C2410C"/>
          <ellipse cx="254" cy="126" rx="35" ry="6.5" fill="#EA580C"/>
          <g fill="#FDBA74"><circle cx="238" cy="138" r="2"/><circle cx="254" cy="141" r="2"/><circle cx="270" cy="138" r="2"/></g>
          <path d="M254 80c11 15 13 28 0 41-13-13-11-26 0-41z" fill="#FBBF24"/>
          <path d="M254 97c5 8 6 15 0 21-6-6-5-13 0-21z" fill="#FEF3C7"/>
          <g class="ap-alt__banner-type">
            <text x="24" y="50" font-size="13" font-weight="700" letter-spacing="3" fill="#FDE68A">DIWALI SALE</text>
            <text x="21" y="102" font-size="48" font-weight="800" fill="#FFFFFF">30% off</text>
            <text x="24" y="130" font-size="18" font-weight="600" fill="#E0E7FF">all lamps</text>
            <text x="24" y="162" font-size="12.5" fill="#C7D2FE">Until 2 November</text>
          </g>
        </svg>
      </div>
      </div>
      <p class="ap-alt__code"><code data-alt-right>alt="Diwali sale: 30% off all lamps, until 2 November"</code><code data-alt-wrong hidden>alt="Sale banner"</code></p>
      <div class="ap-alt__hear">
        <p class="ap-alt__say-k"><svg class="ap-alt__say-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M11 5 6 9H3v6h3l5 4V5Z"/><path d="M15.5 8.5a5 5 0 0 1 0 7M18.5 5.5a9 9 0 0 1 0 13"/></svg><span>Screen reader says</span></p>
        <p class="ap-alt__speech" data-alt-right>Diwali sale: 30% off all lamps, until 2 November, image</p>
        <p class="ap-alt__speech" data-alt-wrong hidden>Sale banner, image</p>
        <p class="ap-alt__why ap-alt__why--right" data-alt-right><svg class="ap-alt__mark" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12 5 5L20 7"/></svg><span><strong>Right.</strong> Repeats the words in the picture, because they are the point of it.</span></p>
        <p class="ap-alt__why ap-alt__why--wrong" data-alt-wrong hidden><svg class="ap-alt__mark" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6 6l12 12M18 6 6 18"/></svg><span><strong>Wrong.</strong> The offer and the closing date, the reasons the banner is there, are lost.</span></p>
      </div>
      <button type="button" class="ap-btn ap-alt__try" aria-pressed="false" data-alt-try>
        <svg class="ap-btn__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 12a8 8 0 0 1 14-5.3L20 9"/><path d="M20 4v5h-5"/><path d="M20 12a8 8 0 0 1-14 5.3L4 15"/><path d="M4 20v-5h5"/></svg>
        Try the wrong alt
      </button>
    </li>
  </ul>

  <section class="ap-alt__tree" aria-labelledby="ialt-tree-name" data-alt-tree>
    <div class="ap-alt__tree-top">
      <svg class="ap-alt__tree-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="6" cy="5" r="2.5"/><circle cx="6" cy="19" r="2.5"/><circle cx="18" cy="12" r="2.5"/><path d="M6 7.5v9M8.3 6.2C13 7 15.5 8.5 16 10M8.3 17.8c4.7-.8 7.2-2.3 7.7-3.8"/></svg>
      <div>
        <h3 class="ap-alt__name" id="ialt-tree-name">Which alt does my image need?</h3>
        <p class="ap-alt__lead">Answer up to four questions; the advice appears below them.</p>
      </div>
    </div>
    <ol class="ap-alt__qs">
      <li class="ap-alt__q" data-q="link">
        <fieldset class="ap-alt__set">
          <legend class="ap-alt__legend">Is the image the only thing inside a link or a button?</legend>
          <div class="ap-alt__yn">
            <label class="ap-alt__opt"><input type="radio" name="ialt-q-link" value="yes" /><span>Yes</span></label>
            <label class="ap-alt__opt"><input type="radio" name="ialt-q-link" value="no" /><span>No</span></label>
          </div>
        </fieldset>
      </li>
      <li class="ap-alt__q" data-q="text" hidden>
        <fieldset class="ap-alt__set">
          <legend class="ap-alt__legend">Does the image show words that people need to read?</legend>
          <div class="ap-alt__yn">
            <label class="ap-alt__opt"><input type="radio" name="ialt-q-text" value="yes" /><span>Yes</span></label>
            <label class="ap-alt__opt"><input type="radio" name="ialt-q-text" value="no" /><span>No</span></label>
          </div>
        </fieldset>
      </li>
      <li class="ap-alt__q" data-q="near" hidden>
        <fieldset class="ap-alt__set">
          <legend class="ap-alt__legend">Are the same words already on the page as real text?</legend>
          <div class="ap-alt__yn">
            <label class="ap-alt__opt"><input type="radio" name="ialt-q-near" value="yes" /><span>Yes</span></label>
            <label class="ap-alt__opt"><input type="radio" name="ialt-q-near" value="no" /><span>No</span></label>
          </div>
        </fieldset>
      </li>
      <li class="ap-alt__q" data-q="meaning" hidden>
        <fieldset class="ap-alt__set">
          <legend class="ap-alt__legend">Does the image tell people something the text around it does not?</legend>
          <div class="ap-alt__yn">
            <label class="ap-alt__opt"><input type="radio" name="ialt-q-meaning" value="yes" /><span>Yes</span></label>
            <label class="ap-alt__opt"><input type="radio" name="ialt-q-meaning" value="no" /><span>No</span></label>
          </div>
        </fieldset>
      </li>
      <li class="ap-alt__q" data-q="complex" hidden>
        <fieldset class="ap-alt__set">
          <legend class="ap-alt__legend">Is it a chart, diagram or map that needs more than a sentence?</legend>
          <div class="ap-alt__yn">
            <label class="ap-alt__opt"><input type="radio" name="ialt-q-complex" value="yes" /><span>Yes</span></label>
            <label class="ap-alt__opt"><input type="radio" name="ialt-q-complex" value="no" /><span>No</span></label>
          </div>
        </fieldset>
      </li>
    </ol>
    <div class="ap-alt__verdict">
      <div class="ap-alt__answer" data-verdict="function" hidden>
        <p class="ap-alt__answer-k">Advice</p>
        <h4 class="ap-alt__answer-name">Name it for what it does</h4>
        <p class="ap-alt__answer-text">Describe where the link goes or what the button does, not how the picture looks, like the logo above that links home.</p>
        <p class="ap-alt__answer-code"><code>alt="Neel Pottery home"</code></p>
      </div>
      <div class="ap-alt__answer" data-verdict="words" hidden>
        <p class="ap-alt__answer-k">Advice</p>
        <h4 class="ap-alt__answer-name">Put the words in the alt</h4>
        <p class="ap-alt__answer-text">Repeat the text in the image, like the sale banner's offer and date. Better still, make it real text styled with CSS, so it can be resized and translated.</p>
        <p class="ap-alt__answer-code"><code>alt="Diwali sale: 30% off all lamps, until 2 November"</code></p>
      </div>
      <div class="ap-alt__answer" data-verdict="redundant" hidden>
        <p class="ap-alt__answer-k">Advice</p>
        <h4 class="ap-alt__answer-name">Leave the alt empty</h4>
        <p class="ap-alt__answer-text">The words are already on the page, so reading them twice adds nothing. An empty alt tells screen readers to skip the image.</p>
        <p class="ap-alt__answer-code"><code>alt=""</code></p>
      </div>
      <div class="ap-alt__answer" data-verdict="decorative" hidden>
        <p class="ap-alt__answer-k">Advice</p>
        <h4 class="ap-alt__answer-name">Leave the alt empty</h4>
        <p class="ap-alt__answer-text">The image is decoration, like the divider above. alt="" on an img, or aria-hidden="true" on an inline SVG, keeps it out of the way.</p>
        <p class="ap-alt__answer-code"><code>alt=""</code></p>
      </div>
      <div class="ap-alt__answer" data-verdict="describe" hidden>
        <p class="ap-alt__answer-k">Advice</p>
        <h4 class="ap-alt__answer-name">Describe what matters, in a sentence</h4>
        <p class="ap-alt__answer-text">Say what the image shows that matters here, the way you would on the phone. Leave out "image of": screen readers already say it is an image.</p>
        <p class="ap-alt__answer-code"><code>alt="White teapot hand-painted with cobalt-blue flowers"</code></p>
      </div>
      <div class="ap-alt__answer" data-verdict="complex" hidden>
        <p class="ap-alt__answer-k">Advice</p>
        <h4 class="ap-alt__answer-name">A short alt, then a long description</h4>
        <p class="ap-alt__answer-text">Name the chart and its main point in the alt, then give the detail as text or a table beside it, as the Complex image pattern does.</p>
        <p class="ap-alt__answer-code"><code>alt="Bar chart: most of Mumbai's rain falls from June to September"</code></p>
      </div>
    </div>
    <button type="button" class="ap-btn ap-btn--ghost ap-alt__restart" data-alt-restart hidden>
      <svg class="ap-btn__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 12a8 8 0 1 0 2.4-5.7"/><path d="M4 4v4.5h4.5"/></svg>
      Start again
    </button>
  </section>
  <p class="ap-alt__sr" role="status" data-alt-status></p>
</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 picture gets the alternative its job needs: a description, a destination, its own words, or nothing for decoration.

  • 1.3.1 Info and Relationships Level A

    Each question of the decision tree is a fieldset whose legend names its Yes and No radio buttons.

  • 1.4.5 Images of Text Level AA

    The sale banner shows why images of text are a last resort, and its alt carries every word it shows.

  • 2.4.4 Link Purpose (In Context) Level A

    The logo link is named by its alt, "Neel Pottery home", so its purpose is clear out of context.

  • 4.1.2 Name, Role, Value Level A

    Try the wrong alt is a toggle button with aria-pressed, so its state is announced, not only drawn.

  • 4.1.3 Status Messages Level AA

    What a screen reader would say, and the advice at the end of the tree, are announced in a polite status message without moving focus.

Usage

When to use it

Use it

  • Every img element, and every inline SVG or canvas that stands for a picture.
  • Pictures inside links and buttons, where the alt is the control's name.
  • Writing guidance for an editorial or product team that adds images every day.

Use something else

  • Charts, maps and diagrams: a short alt is not enough, so add a long description as in the complex image pattern.
  • Icons beside a visible label: hide the icon and let the label name the control.
  • Text you could set as real text: style it with CSS instead of drawing it into a picture.

Common failures

How it usually goes wrong

  • No alt attribute at all

    Many screen readers then read the file name, like "teapot-final-v2.jpg", which tells nobody anything. Every img needs alt, even if it is empty.

  • "Image of" or "picture of"

    Screen readers already say "image", so the words are heard twice. Start with what the picture shows.

  • Describing decoration

    A described flourish or stock photo interrupts the content for nothing. Decoration gets alt="" or aria-hidden.

  • An empty alt on a linked image

    When the picture is all the link holds, an empty alt leaves a link with no name. Name where it goes.

  • Alt that leaves out the text in the picture

    "Sale banner" drops the discount and the date. If the words matter, they belong in the alt.

  • The same text in alt and the caption

    Repeating a visible caption word for word makes people hear it twice. Let the alt add what the caption does not say.

Notes

Building it

  • For an inline SVG that stands for a picture, put role="img" and aria-label on the svg element; children of role="img" are hidden, so its own text and shapes are not read.
  • For a decorative SVG, use aria-hidden="true"; for a decorative img, alt="". Both take the picture out of the accessibility tree without hiding it from view.
  • Alt depends on context: the teapot photo describes the pattern because the product name is already beside it. On a page about tea, the same photo might need different words.
  • Keep it short, usually one sentence. If a picture needs a paragraph, it is a complex image: give it a short alt and a long description beside it.
  • The demo's Try the wrong alt buttons change the live markup, so the inspector and your screen reader both meet the wrong version.

Sources: W3C WAI: An alt decision tree · W3C WAI: Images tutorial · HTML: requirements for providing text to act as an alternative for images

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