Give it a try

  1. To see your bookmarks bar, press the keys (Ctrl/Cmd + Shift + B) .
  2. Drag the "SpotCheck - Image Alt Checker" link to your bookmarks bar or bookmark folder.
  3. Open any page, then click the bookmark to run it.
  4. Click it again or click the close button in the popup to clear the results.

Example of the popup that will appear in the bottom right corner of your browser with a summary of the results.

Image Alt Checker 2 correct   1 flagged   1 incorrect

What the colors mean

Each image gets a colored outline and a badge showing what was found.

Correct

The image has clear, descriptive alt text, or is correctly marked as decorative with an empty alt attribute (alt="").

Flagged

The image has alt text, but it looks like a filename or starts with a redundant phrase like "image of", worth rewriting to describe the content or purpose instead.

Incorrect

The image has no alt attribute at all, so screen readers may announce the filename or nothing meaningful.

Try it here

Run the check against the sample markup below without leaving this page.

Two hikers at a mountain summit IMG_2048.jpg

Applicable WCAG 2.2 success criteria

A text alternative is what lets a screen reader convey what an image shows, or tell the user to skip over one that is purely decorative.

  • 1.1.1
    Non-text Content(Level A)

    Every image needs a text alternative: descriptive alt text for meaningful images, or an empty alt for ones that are purely decorative.

Read the source

The bookmarklet contains the TL;DR version of the code. Here's the readable version.

(function () {
  var NS = '__a11yImageAltChecker';
  if (window[NS] && window[NS].active) {
    window[NS].teardown();
    return;
  }

  function looksLikeFilename(alt) {
    var trimmed = alt.trim();
    return /\.(jpg|jpeg|png|gif|bmp|webp|svg|tiff?)$/i.test(trimmed) ||
      /^(img|image|dsc|photo|screenshot)[-_ ]?\d/i.test(trimmed);
  }

  function hasRedundantPhrase(alt) {
    return /^(image|photo|picture|graphic|icon)\s+of\b/i.test(alt.trim());
  }

  function isVisible(el) {
    var style = getComputedStyle(el);
    if (style.display === 'none' || style.visibility === 'hidden') return false;
    var rect = el.getBoundingClientRect();
    return rect.width > 0 && rect.height > 0;
  }

  var images = Array.prototype.filter.call(document.querySelectorAll('img'), isVisible);
  var records = [];
  var counts = { green: 0, gold: 0, red: 0 };

  // <img> is always a replaced element: browsers don't render light-DOM
  // children appended into it, so the badge is attached to <body> instead
  // and positioned to line up with the image itself.
  function mark(el, level, label) {
    records.push({ el: el, outline: el.style.outline, outlineOffset: el.style.outlineOffset, badge: null });
    var color = level === 'green' ? '#1a7d4f' : level === 'gold' ? '#8b6800' : '#be412a';
    el.style.outline = '3px solid ' + color;
    el.style.outlineOffset = '2px';

    var badge = document.createElement('div');
    badge.textContent = label;
    badge.setAttribute('aria-hidden', 'true');
    badge.style.cssText = [
      'position:absolute', 'background:' + color, 'color:#fff',
      'font:600 14px monospace', 'padding:1px 5px', 'border-radius:3px 3px 0 0',
      'z-index:2147483000', 'pointer-events:none', 'white-space:nowrap',
      'max-width:min(90vw, 24rem)', 'overflow:hidden', 'text-overflow:ellipsis'
    ].join(';');
    document.body.appendChild(badge);
    var rect = el.getBoundingClientRect();
    var badgeRect = badge.getBoundingClientRect();
    badge.style.top = (rect.top + window.scrollY - badgeRect.height) + 'px';
    badge.style.left = (rect.left + window.scrollX) + 'px';

    records[records.length - 1].badge = badge;
    counts[level]++;
  }

  images.forEach(function (img) {
    if (!img.hasAttribute('alt')) {
      mark(img, 'red', 'No alt attribute');
      return;
    }
    var alt = img.getAttribute('alt');
    if (alt.trim() === '') {
      mark(img, 'green', 'Decorative (empty alt)');
      return;
    }
    if (looksLikeFilename(alt)) {
      mark(img, 'gold', 'Alt text looks like a filename: "' + alt + '"');
    } else if (hasRedundantPhrase(alt)) {
      mark(img, 'gold', 'Redundant phrase in alt text: "' + alt + '"');
    } else {
      mark(img, 'green', 'OK: "' + alt + '"');
    }
  });

  var panel = document.createElement('div');
  panel.setAttribute('data-a11y-bm-panel', 'image-alt-checker');
  panel.setAttribute('role', 'status');
  panel.setAttribute('aria-live', 'polite');
  panel.style.cssText = [
    'position:fixed', 'bottom:16px', 'right:16px', 'z-index:2147483001',
    'background:#1b2430', 'color:#e7eaed', 'font:13px/1.5 -apple-system,sans-serif',
    'padding:12px 32px 12px 16px', 'border-radius:6px', 'box-shadow:0 4px 16px rgba(0,0,0,.3)',
    'max-width:260px'
  ].join(';');
  panel.innerHTML =
    '<strong style="display:block;margin-bottom:4px;">Image Alt Text Checker</strong>' +
    '<span style="color:#3fbf7f;">&#9679;</span> ' + counts.green + ' correct &nbsp; ' +
    '<span style="color:#e0b03f;">&#9679;</span> ' + counts.gold + ' flagged &nbsp; ' +
    '<span style="color:#e06a4f;">&#9679;</span> ' + counts.red + ' incorrect';
  var closeBtn = document.createElement('button');
  closeBtn.type = 'button';
  closeBtn.textContent = '\u00d7';
  closeBtn.setAttribute('aria-label', 'Close');
  closeBtn.style.cssText = [
    'position:absolute', 'top:4px', 'right:6px',
    'background:transparent', 'border:none', 'color:#e7eaed',
    'font:16px/1 -apple-system,sans-serif', 'cursor:pointer',
    'padding:2px 6px', 'border-radius:4px', 'line-height:1'
  ].join(';');
  closeBtn.addEventListener('click', function () { window[NS].teardown(); });
  panel.appendChild(closeBtn);
  document.body.appendChild(panel);

  function teardown() {
    records.forEach(function (r) {
      r.el.style.outline = r.outline;
      r.el.style.outlineOffset = r.outlineOffset;
      if (r.badge && r.badge.parentNode) r.badge.parentNode.removeChild(r.badge);
    });
    if (panel.parentNode) panel.parentNode.removeChild(panel);
  }

  window[NS] = {
    active: true,
    teardown: function () {
      teardown();
      window[NS].active = false;
    }
  };
})();

FYI

  • An empty alt attribute alt="" is one suitable way to mark a decorative image.
  • Alt text that repeats the filename or starts with "image", "picture", or "photo" adds noise without adding information. Screen readers already announce that it is an image.
  • This check only looks at <img> elements. CSS background images and SVG icons need their own accessible-name treatment, which this tool does not cover yet.