Inventory Every Image in Your HTML

Paste HTML and get one table row per image source — alt text, width, height, loading, decoding, srcset and sizes — with every image that is missing alt text or explicit dimensions flagged. Runs entirely in your browser; nothing is uploaded.

Try:
Image inventory

About this tool

Paste the HTML of a page, a template fragment, or an email body and this tool walks the markup with a real HTML parser and builds one table row for every image source it declares — every <img>, and every <picture><source> candidate behind it — together with the attributes that decide how that image renders:

Two defects are flagged, both of them named audit rules rather than opinions:

A third, no-source, catches an <img> with neither src nor srcset — usually a broken template variable.

Worked example

Input HTML:

<article>
  <img src="/hero.jpg" alt="Team on a rooftop" width="1200" height="800" decoding="async" fetchpriority="high">
  <img src="/promo.png" width="600" height="400" loading="lazy">
  <img src="/divider.svg" alt="" width="8" height="8">
  <img src="/chart.png" alt="Revenue by quarter" loading="lazy" decoding="async">
</article>

Output with Markdown selected:

## Image inventory

4 images, 1 missing alt, 1 missing dimensions, 1 decorative (alt=""), 2 lazy-loaded

| # | Element | Source | Alt | Size | Loading | Decoding | Issues |
|---|---------|--------|-----|------|---------|----------|--------|
| 1 | img | `/hero.jpg` | Team on a rooftop | 1200×800 | — | async | — |
| 2 | img | `/promo.png` | — | 600×400 | lazy | — | missing-alt |
| 3 | img | `/divider.svg` | *(decorative)* | 8×8 | — | — | — |
| 4 | img | `/chart.png` | Revenue by quarter | — | lazy | async | missing-width, missing-height |

Row 3 is clean even though its alt is empty — that divider is decorative and correctly marked. Row 4 is the layout-shift problem: it has good alt text but no dimensions at all. Turn on Only flagged images to get just rows 2 and 4, or switch to CSV for a spreadsheet and JSON for a script.

Good uses

Limits and edge cases

FAQ

Why is an image with alt="" not flagged?

Because an empty alt is correct markup, not a mistake. alt="" tells a screen reader "this image carries no information, skip it" — the right thing for spacers, dividers, decorative flourishes, and an icon that sits next to a text label that already says the same thing. Removing the attribute entirely is what causes trouble: with no alt at all, many screen readers fall back to reading out the file name, so a user hears "I M G underscore 4 0 3 2 dot jpeg".

The tool therefore reports three states — present, empty, and missing — and only flags missing. If you are auditing whether each decorative marking was deliberate, turn on Flag decorative alt="" too and every empty alt is listed as well.

Why is width="50%" flagged as missing?

Because the width and height content attributes in HTML must be valid non-negative integers — unitless numbers of pixels. 50%, auto, and 10.5 are not, so the browser cannot use them to work out the image's aspect ratio and reserve space for it before the file arrives. The page still shifts as the image loads, which is exactly the problem the check exists to catch.

The value is reported verbatim in the table so you can see what is actually there, and flagged so it does not quietly pass. The fix is to put the intrinsic pixel size in the attributes and do the proportional sizing in CSS (width: 50%; height: auto) — the two work together, and modern browsers derive the aspect ratio from the attributes.

How are <picture> and <source> handled?

Each <picture> is numbered, and every <source> inside it becomes its own row labelled source (picture N), followed by the <img> fallback labelled img (picture N) — the same order as the markup, which is also the order the browser evaluates the candidates in.

A <source> row shows its srcset, its media query, and its type, all listed under Responsive sources below the table where there is room for the full value. It shows n/a in the Alt column, because a <source> has no alt of its own — the <img> at the end of the <picture> supplies the alt text for whichever candidate the browser picks, so that is the only row where alt is audited.

Turn off Include <picture> sources to collapse the table down to just the <img> rows; the count of sources found still appears in the summary.

Can it read the images from a URL instead of pasted HTML?

No. This tool is fully offline — it has no network access at all, which is why your markup never leaves your machine. That also means it never downloads the image files, so it reports what the attributes say rather than what the files are: real pixel dimensions, real file size, real format, and broken links are all outside what a markup parser can honestly tell you.

Fetch the page yourself (View Source, curl, or DevTools → Elements → Copy outerHTML) and paste the result here. Pasting is often the only option anyway for a staging site behind a login, a CMS template fragment, or an HTML email — none of which a URL-based checker can reach.

Does it handle messy or invalid HTML?

Yes. Parsing uses the same HTML5 parsing algorithm browsers use, so unquoted attribute values (<img src=/a.png alt=Hello>), unclosed <p> and <li> tags, uppercase tag and attribute names, and sloppy nesting all parse the way a browser would read them. That is the main reason to use this rather than a regular expression over <img[^>]*>, which breaks on all of the above — and cannot see the <picture><source><img> structure at all, since that relationship lives in the tree rather than in any single tag.

What do the loading, decoding, and fetchpriority columns tell me?

They are the browser hints that decide when each image is fetched and painted, and they are reported verbatim so you can check them against your intent:

loading="lazy" defers the fetch until the image is near the viewport — right for everything below the fold, and wrong for your hero, because lazy-loading the largest above-the-fold image directly delays Largest Contentful Paint. decoding="async" lets the browser decode the image off the main thread so it does not block rendering. fetchpriority="high" pushes one image to the front of the queue, and is normally used on exactly one image per page — the LCP one.

A common finding: a hero row showing lazy in the Loading column, or every image on the page showing fetchpriority=high, which means none of them is actually prioritised.

How do I get just the problems, without the images that already pass?

Turn on Only flagged images. The table then contains only rows with at least one issue token, renumbered from 1, and a line underneath tells you how many clean rows were hidden so the totals still add up. The summary above the table always counts every image found, filtered or not.

If nothing is flagged you get "No issues found — every image has alt text and explicit dimensions" rather than an error, so the filter is safe to leave switched on in a script or a deep link.

Developer & Automation Access

Run it from the terminal

Same engine as this page, headless — via the gizza CLI:

gizza tool html-image-inventory '<img src="/hero.jpg" alt="Team on a rooftop" width="1200" height="800">
<img src="/promo.png" loading="lazy">'

New to the CLI? Get gizza →

Open it by URL

Pre-fill and auto-run this tool with query parameters — the names match the API/CLI:

https://gizza.ai/tools/html-image-inventory/?html=%3Cimg%20src%3D%22%2Fhero.jpg%22%20alt%3D%22Team%20on%20a%20rooftop%22%20width%3D%221200%22%20height%3D%22800%22%3E%0A%3Cimg%20src%3D%22%2Fpromo.png%22%20loading%3D%22lazy%22%3E&format=markdown&include_sources=true&only_issues=true&flag_empty_alt=true&include_summary=true

Machine-readable descriptor: tool.json — title + parameters JSON Schema for agents.