Give it a try

SpotCheck - Alt Text Checker

last updated: 08/04/2026

  1. To see your bookmarks bar, press the keys (Ctrl/Cmd + Shift + B) .
  2. Drag the "SpotCheck - Alt Text 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.

Two hikers at a mountain summit Photo of a dog 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 w = window;
  var d = document;
  var STATE_KEY = "__a11yImageAltChecker";

  if (w[STATE_KEY]) {
    w[STATE_KEY].clear();
    return;
  }

  var COLORS = {
    green: "#1a7d4f",
    gold: "#8b6800",
    red: "#be412a"
  };

  var OUTLINES = {
    green: "5px solid",
    gold: "6px dotted",
    red: "5px dashed"
  };

  var OUTLINE_OFFSET = "3px";

  var DOTS = {
    green: "#3fbf7f",
    gold: "#e0b84f",
    red: "#e06a4f"
  };

  var LEVELS = {
    ok: "green",
    decorative: "green",
    filename: "gold",
    redundant: "gold",
    missing: "red"
  };

  var BADGES = {
    ok: "Alt text",
    decorative: "Decorative (empty alt)",
    filename: "Alt text looks like a filename",
    redundant: "Redundant phrase in alt text",
    missing: "No alt attribute"
  };

  var QUOTED_KINDS = ["ok", "filename", "redundant"];

  var FILENAME_PATTERNS = [
    /\.(jpg|jpeg|png|gif|bmp|webp|svg|tiff?)$/i,
    /^(img|image|dsc|photo|screenshot)[-_ ]?\d/i
  ];

  var REDUNDANT_PATTERN = /^(image|photo|picture|graphic|icon)\s+of\b/i;

  var records = [];
  var counts = { green: 0, gold: 0, red: 0 };
  var panel = null;

  function normalize(value) {
    return (value || "").replace(/\s+/g, " ").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;
  }

  function looksLikeFilename(alt) {
    var trimmed = alt.trim();
    return FILENAME_PATTERNS.some(function (pattern) {
      return pattern.test(trimmed);
    });
  }

  function classify(el) {
    if (!el.hasAttribute("alt")) {
      return "missing";
    }
    var alt = el.getAttribute("alt");
    if (!alt.trim()) {
      return "decorative";
    }
    if (looksLikeFilename(alt)) {
      return "filename";
    }
    if (REDUNDANT_PATTERN.test(alt.trim())) {
      return "redundant";
    }
    return "ok";
  }

  function labelFor(el, kind) {
    if (QUOTED_KINDS.indexOf(kind) === -1) {
      return BADGES[kind];
    }
    return BADGES[kind] + ': "' + normalize(el.getAttribute("alt")) + '"';
  }

  function makeBadge(el, kind) {
    var level = LEVELS[kind];
    var badge = d.createElement("div");

    badge.setAttribute("data-a11y-alt-check", "badge");
    badge.setAttribute("aria-hidden", "true");
    badge.textContent = labelFor(el, kind);
    badge.style.cssText = [
      "position:absolute",
      "width:fit-content",
      "max-width:min(90vw, 24rem)",
      "padding:4px 8px",
      "border-radius:4px",
      "background:" + COLORS[level],
      "color:#fff",
      "font:500 16px/1.2 Arial, Helvetica, \"Helvetica Neue\", sans-serif",
      "pointer-events:none",
      "z-index:2147483646"
    ].join(";");
    return badge;
  }

  function placeBadge(el, badge) {
    var rect = el.getBoundingClientRect();
    var badgeRect = badge.getBoundingClientRect();

    badge.style.top = rect.top + w.scrollY - badgeRect.height + "px";
    badge.style.left = rect.left + w.scrollX + "px";
  }

  function mark(el) {
    var kind = classify(el);
    var level = LEVELS[kind];
    var record = {
      el: el,
      outline: el.style.outline,
      offset: el.style.outlineOffset,
      badge: makeBadge(el, kind)
    };

    counts[level] += 1;
    el.style.outline = OUTLINES[level] + " " + COLORS[level];
    el.style.outlineOffset = OUTLINE_OFFSET;
    d.body.appendChild(record.badge);
    placeBadge(el, record.badge);
    records.push(record);
  }

  function makeCountLine(level, word) {
    var line = d.createElement("p");
    var dot = d.createElement("span");

    line.style.cssText = "margin:0;padding:0";
    dot.textContent = "\u25cf";
    dot.style.cssText = "color:" + DOTS[level] + ";margin-right:6px";
    line.appendChild(dot);
    line.appendChild(d.createTextNode(counts[level] + " " + word));
    return line;
  }

  function makePanel() {
    var wrapper = d.createElement("div");
    var heading = d.createElement("strong");
    var close = d.createElement("button");

    wrapper.setAttribute("data-a11y-alt-check", "panel");
    wrapper.setAttribute("role", "status");
    wrapper.style.cssText = [
      "position:fixed",
      "right:16px",
      "bottom:16px",
      "max-width:260px",
      "padding:12px 32px 12px 16px",
      "border-radius:6px",
      "background:#1b2430",
      "color:#e7eaed",
      "font:400 14px/1.5 Arial, Helvetica, \"Helvetica Neue\", sans-serif",
      "box-shadow:0 8px 24px rgba(0,0,0,0.35)",
      "z-index:2147483647"
    ].join(";");

    heading.textContent = "Image Alt Text Checker";
    heading.style.cssText = "display:block;margin-bottom:4px";
    wrapper.appendChild(heading);

    wrapper.appendChild(makeCountLine("green", "correct"));
    wrapper.appendChild(makeCountLine("gold", "flagged"));
    wrapper.appendChild(makeCountLine("red", "incorrect"));

    close.type = "button";
    close.textContent = "\u00d7";
    close.setAttribute("aria-label", "Clear the alt text check results");
    close.style.cssText = [
      "position:absolute",
      "top:6px",
      "right:6px",
      "width:24px",
      "height:24px",
      "padding:2px 6px",
      "border:none",
      "background:transparent",
      "color:#e7eaed",
      "font:16px/1 Arial, Helvetica, \"Helvetica Neue\", sans-serif",
      "cursor:pointer"
    ].join(";");
    close.addEventListener("click", clear);
    wrapper.appendChild(close);

    return wrapper;
  }

  function clear() {
    records.forEach(function (record) {
      record.el.style.outline = record.outline;
      record.el.style.outlineOffset = record.offset;
      if (!normalize(record.el.getAttribute("style"))) {
        record.el.removeAttribute("style");
      }
      if (record.badge.parentNode) {
        record.badge.parentNode.removeChild(record.badge);
      }
    });
    records = [];
    if (panel && panel.parentNode) {
      panel.parentNode.removeChild(panel);
    }
    panel = null;
    delete w[STATE_KEY];
  }

  Array.prototype.slice
    .call(d.querySelectorAll("img"))
    .filter(isVisible)
    .forEach(mark);

  panel = makePanel();
  d.body.appendChild(panel);
  w[STATE_KEY] = { clear: clear };
})();

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.