Pillar guide

Alt text for product images, done properly

Five kinds of image, one question that sorts them all, the WordPress and WooCommerce plumbing underneath, and an honest account of what a model can and cannot decide for you.

Published 2026-08-04 19-minute read 19 primary sources, dated

By the end of this guide you should be able to look at any image in your shop and place it, within a couple of seconds, in one of five jobs — and know from that job what its alt text should say, or whether it should deliberately say nothing at all. You will also know where WordPress and WooCommerce keep that text, what WooCommerce may write into your gallery when the field is blank, why the two most popular bulk fixes make things worse, and how to judge an alt text you did not write.

Most stores do not have an “images missing alt text” problem in the simple sense. They have a few hundred images with nothing at all, and a few thousand carrying text that satisfies a checker and helps nobody. Those two need opposite fixes, and telling them apart is most of the work.

What a text alternative actually is

Success Criterion 1.1.1 Non-text Content, Level A, is one sentence: “All non-text content that is presented to the user has a text alternative that serves the equivalent purpose, except for the situations listed below.”

The operative words are equivalent purpose. A text alternative is not a caption and not a description — it is a substitute. Delete the image, drop the alt text into the hole it leaves, and read the page: if the page still does its job, the alt text is right. Every rule below follows from that test.

Two of the criterion’s listed exceptions do the heavy lifting in a shop. The first covers controls: “If non-text content is a control or accepts user input, then it has a name that describes its purpose.” The second covers decoration: “If non-text content is pure decoration, is used only for visual formatting, or is not presented to users, then it is implemented in a way that it can be ignored by assistive technology.”

So “ignorable” is a specified, legitimate outcome, and a gallery thumbnail is not a picture at all — it is a control, and controls get names rather than descriptions. Note also that this is Level A, the least demanding tier of the standard whose current W3C Recommendation is dated 12 December 2024.

It serves more people than the obvious ones: it is what a screen-reader user hears, what shows when an image fails to load, what a voice-control user says aloud to operate an image button, and what a Braille display renders. Search engines read it too — written for the shopper it serves both, written for the crawler it serves neither well.

Why an EU-facing shop should care about a Level A failure

Directive (EU) 2019/882, the European Accessibility Act, was published in the Official Journal on 7 June 2019. Article 2(2)(f) brings e-commerce services into scope where they are provided to consumers “after 28 June 2025”, and Article 3, point (30) defines those as “services provided at a distance, through websites and mobile device-based services by electronic means and at the individual request of a consumer with a view to concluding a consumer contract”. That is a description of a WooCommerce store.

That is the scope and the date, from the Directive itself. Anyone quoting you a penalty figure is quoting a national implementation, and those differ country by country — read the relevant national gazette rather than a vendor’s summary. We are not lawyers.

One question sorts every image

Before writing a word, answer this: what job is this image doing on this page? W3C publishes a decision tree for the purpose, and its questions are about function rather than content — “Does the image contain text?”, is it used in a link or a button, “Does the image contribute meaning to the current page or context?”, “Is the image purely decorative or not intended for users?”

Those questions resolve to five jobs: informative, functional, decorative, image of text, and complex. Here is what the five look like on a shop, with the informative and functional cases split out because a single product gallery contains both.

The imageJob it is doingWhat the alt should carryExample
Main product photoInformative — it is the goodsThe item as a shopper would identify it, plus what the photo shows that the copy does notalt="Ridged stoneware mug in matte oatmeal, handle to the right"
Second gallery photoInformative — adds new informationOnly the new information; never a repeat of the first photoalt="The same mug held in one hand, roughly a fist wide"
Lifestyle shotInformative — sells context, not specificationThe context that is doing the sellingalt="The oatmeal mug on a kitchen windowsill beside a folded linen towel"
Gallery thumbnail that swaps the main imageFunctional — it is a controlThe view it selects, not the picturealt="Show the handle detail"
Colour swatch, image onlyFunctional — it carries the option nameThe option namealt="Ocean blue"
Divider, texture, flourishPure decorationNothing, and no title attribute eitheralt=""
Sale banner with the offer set as pixelsImage of textThe words, verbatim — then fix the root causealt="Winter sale — everything reduced until Sunday"
Size chart or spec table as an imageComplexA short name plus a pointer; the data goes into the page as real textalt="Size chart. Full measurements in the table below."
Image used as a submit controlControl — it needs a name, not a descriptionThe actionalt="Search"

That last row is a separate automated rule for a reason: an <input type="image"> with no alt is a nameless control, not merely an undescribed picture.

The five images every store has

The product photo

Write what a shopper would need in order to recognise the item if the photo vanished: form, material, colour, finish, and the angle where the angle carries information. Resist two temptations. The first is the SEO title — “Handmade Ceramic Coffee Mug Oatmeal 350ml Gift Idea Kitchen Homeware” is a keyword bag, not an alternative, and it gets read out in full. The second is the adjective pile: “beautiful artisanal” is not information anyone can act on.

If the product name is already announced immediately before the image — as a heading, or as the link text on a listing card — you are allowed to be brief, and sometimes silent. W3C’s guidance on functional images gives exactly that case a null alt, because “the image does not represent different functionality or convey other information than that already provided in the link text”.

The lifestyle shot

Lifestyle photography is where alt text collapses into “product in use”. Ask instead what the shot is for commercially, and describe that. Scale, setting and styling are the usual answers: how big the thing is in a real hand, what room it belongs in, what it is worn with. If it genuinely adds nothing beyond mood on a page that already carries the product photo, empty alt is defensible — but say that out loud first, because “it’s just mood” is the excuse that quietly deletes half a gallery.

The decorative flourish

Decoration takes alt="". Two details matter more than people expect.

The first is the difference between empty and absent. W3C’s decorative-images guidance says a null alt “should be provided (alt="") so that they can be ignored by assistive technologies, such as screen readers”, and warns that “Leaving out the alt attribute is also not an option because when it is not provided, some screen readers will announce the file name of the image instead”. Empty and missing produce different outcomes: one is silence, the other is DSC_0194-scaled-1.jpg.

The second is the title attribute. Technique H67 is titled “Using null alt text and no title attribute on img elements for images that assistive technology should ignore”, and its procedure has two checks: “Check that title attribute is either absent or empty” and “Check that alt attribute is present and empty”. A decorative image with alt="" and a leftover title is not ignorable. WordPress makes this easy to get wrong, because the Image block offers a Title attribute field under Advanced settings and suggests using it “to describe the role of this image on the page” — leave it blank on anything decorative.

Better still, move purely decorative imagery into CSS, which never had an alt attribute to get wrong. The Make WordPress accessibility handbook gives the same advice: “When possible, use CSS to insert images that are only decorative, like an icon or ornament.”

The image of text

A promotional banner with the offer, the terms and the code baked into a JPEG is two failures, not one. The alt-text failure is easy: put the words in the alt attribute. The other is structural.

Success Criterion 1.4.5 Images of Text is Level AA: “If the technologies being used can achieve the visual presentation, text is used to convey information rather than images of text”, with exceptions only where the image of text can be visually customised to the user’s requirements, or where a particular presentation is essential — a logotype being the classic case. Web fonts and CSS can achieve almost any banner you can design, so the exception rarely rescues a sale graphic.

The route out is in the editor, not the design tool. The Image block’s Add text over image control converts the block into a Cover block and puts your words on the page as real text over the photograph — selectable, translatable, resizable with the rest of your type. WordPress’s own note on it is the accessibility argument in miniature: use it “when the image is decorative or when the text can remain readable over the image”.

This is also a criterion no scanner can settle for you. Of the 55 Level A and Level AA criteria we track, 1.4.5 is one of the 16 with no automated detection at all, because deciding whether pixels contain text that could have been real text requires looking. If your banners carry the terms of an offer, that is human work — see the audit tier.

The size chart, spec table or fit diagram

A size chart pasted in as an image is a complex image, and complex images need two parts. W3C: “The first part is the short description to identify the image and, where appropriate, indicate the location of the long description. The second part is the long description – a textual representation of the essential information conveyed by the image.”

For a size chart the long description is not prose. It is the table, marked up as a table, in the page — selectable, searchable, translatable, zoomable, and it reflows on a phone, which a 900-pixel-wide JPEG of a grid does not. Fit diagrams, care-symbol keys and comparison charts work the same way. Any time you catch yourself writing a hundred-word alt attribute, you have found a complex image.

Why “product image” and the filename are worse than nothing

A missing alt attribute is at least honest: the checker flags it and you know there is work to do. A wrong one is a false positive in every direction at once — it satisfies the automated rule, vanishes from your reports, and spends the shopper’s attention on nothing.

WCAG names this directly. Failure F30 is “Failure of Success Criterion 1.1.1 and 1.2.1 due to using text alternatives that are not alternatives (e.g., filenames or placeholder text)”, and its examples include placeholder text such as “spacer” or “image” or “picture”, sequence labels like “picture 1”, “picture 2” or “0001”, “0002”, and filenames like “Oct.jpg” or “Chart.jpg”. These are not weak alt text; they are a documented failure of the criterion.

Put that in a shop. A category page with twenty-four products, each card an image link, every image carrying alt="product image". A screen-reader user moving through the grid hears “link, product image” twenty-four times: no way to tell the mugs from the bowls, no way to shop, and nothing in the report suggesting anything is wrong.

The second pattern looks conscientious, which is why it is everywhere: bulk-filling every alt attribute with the product title. On a listing card the title is already the link text, so the shopper hears the product name twice in a row, all the way down the grid. Technique H2 covers the clean version — combine the adjacent image and text into one link and provide “null alternative text on the image to eliminate duplication of text”. Duplication is the thing that technique exists to remove.

For the detection mechanics and the exact markup transform, the image alt text fix page has both, and image-only links that announce as nothing but “link” are covered under empty links.

Galleries, thumbnails and variation swatches

A WooCommerce product gallery is not one kind of image. It is usually three or four, inside the same block of markup.

<!-- 1. Main image: informative. It is the product. -->
<img src="/wp-content/uploads/mug-oatmeal.jpg"
     alt="Ridged stoneware mug in matte oatmeal, handle to the right">

<!-- 2. Thumbnail: functional. A control that changes the main image,
        so name the view it selects, not the picture. -->
<a href="#" class="gallery-thumb">
  <img src="/wp-content/uploads/mug-handle-150x150.jpg" alt="Show the handle detail">
</a>

<!-- 3. Zoom trigger: the link needs a name; if the icon is its only
        content, the icon carries it. -->
<a href="/wp-content/uploads/mug-oatmeal-full.jpg" class="woocommerce-product-gallery__trigger">
  <img src="/assets/zoom.svg" alt="Zoom the product image">
</a>

<!-- 4. Theme chrome: empty alt, no title attribute (H67). -->
<img src="/assets/corner-flourish.svg" alt="">

Variation swatches add a fifth case and a real complication. A swatch is a control whose whole job is to communicate one option value. If it is an image with no visible text beside it, the option name has to live in the accessible name — alt="Ocean blue" — or the choice is available only to people who can see the colour. If a visible “Ocean blue” label sits next to it, the image is redundant and takes alt="".

The complication is where the markup comes from. WooCommerce assembles per-variation data server-side for the add-to-cart form; the code reference notes that get_available_variations() calls get_available_variation() for each variation, performing “image attachment lookups” alongside price and stock HTML. The browser then swaps the displayed image when a shopper picks an option — so a product can have good alt text in its initial HTML and still present an unnamed image the moment someone selects a colour, because the swapped-in image is a different attachment whose alt field was never filled. Audit variation images as their own set, and be sceptical of any tool, ours included, that reports only on the page as first served.

Where WordPress and WooCommerce keep the text

There is one canonical store and several ways to bypass it by accident.

WordPress keeps an attachment’s alt text in post meta under the key _wp_attachment_image_alt, and wp_get_attachment_image() reads it directly:

'alt'   => trim( strip_tags( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ) ),

If that meta is empty the function emits an empty alt. It does not fall back to the caption, the title or the filename — which is correct behaviour, and also why an unfilled library field produces a silent image rather than a noisy one. The Make WordPress accessibility handbook puts the same point from the editor’s side: “If an alt attribute is not inserted in the Media Library, this will be interpreted as alt="" in the front end.”

Four things complicate that:

  1. The block editor keeps its own copy. WordPress documents it precisely: adding alt text in the Media Library “sets it as the default for that image”, it “will automatically carry over” when you insert the image, and “if you change the alt text within the block settings later, that edit only applies to that specific page or post”. Library edits fix future insertions and images rendered from the attachment; they do not rewrite blocks that already carry their own alt.
  2. WooCommerce ships a helper for generating gallery alt. Since 9.3.3 it has included woocommerce_get_alt_from_product_title_and_position(), documented as “Get alt text based on product name and image position in gallery”. Alt built from a product name and a position beats a filename, and it is still not a text alternative for your second photo — on a listing card it is the duplication H2 removes. If your gallery alt reads like a product title with a number after it, read the served HTML before assuming a person wrote it.
  3. The gallery markup is filtered. The single-product template builds each image with wc_get_gallery_image_html() and echoes the result through the woocommerce_single_product_image_thumbnail_html filter, so themes and plugins can rewrite gallery markup wholesale — the alt attribute included — after WordPress has produced it. When alt text looks right in the library and wrong in the HTML, that filter is the first place to look.
  4. Page builders and custom fields. Anything holding an image outside the attachment relationship — a hard-coded <img>, a builder module with its own alt field, a hero in a theme option — carries its own alt text or none, and survives every Media Library clean-up.

Bulk work that helps, and two kinds that do not

Alt text scales by getting an accurate worklist, then working it in the order that recovers the most shopping.

The worklist first, and one trap: attachments with no alt meta row at all do not match a query for an empty value, so the naive query under-reports. wp post list takes “One or more args to pass to WP_Query”, so compare the full set against the set with a non-empty value.

# Every image attachment in the library, one ID per line.
wp post list --post_type=attachment --post_mime_type=image --format=ids \
  | tr ' ' '\n' | sort > all-images.txt

# Those with a non-empty alt text.
wp post list --post_type=attachment --post_mime_type=image \
  --meta_key=_wp_attachment_image_alt --meta_compare='!=' --meta_value='' \
  --format=ids | tr ' ' '\n' | sort > has-alt.txt

# The real worklist: in the library, with no usable alt text.
comm -23 all-images.txt has-alt.txt > needs-alt.txt

# Write one back: wp post meta update <id> <key> [<value>]
wp post meta update 4821 _wp_attachment_image_alt \
  "Ridged stoneware mug in matte oatmeal, handle to the right"

Then prioritise by revenue rather than upload date: best sellers, then category pages, then basket and checkout, then editorial. An inaccessible product page costs you one sale; an inaccessible checkout costs you all of them.

A render-time filter is a reasonable safety net for one narrow job — stopping filenames leaking out of theme chrome — and a bad idea for anything else:

/**
 * Safety net, not a substitute: an image reaching render with no usable alt
 * text is made ignorable rather than left for its file name to be announced.
 */
add_filter( 'wp_get_attachment_image_attributes', function ( $attr, $attachment ) {
	if ( ! isset( $attr['alt'] ) || '' === trim( $attr['alt'] ) ) {
		$attr['alt'] = '';       // explicit and ignorable
		unset( $attr['title'] ); // H67: title absent or empty
	}
	return $attr;
}, 10, 2 );

Be clear about what that does: it makes unlabelled images silent, not described. On decorative theme graphics that is an improvement; on product photography it hides the problem from your shoppers and your reports at once, which is why we would never ship it as blanket remediation.

Two approaches are not worth doing at all: templating alt from the product title site-wide, which passes the presence check, creates the duplication H2 exists to remove, and marks the job done so nobody revisits it; and filling alt with keywords, which F30 covers in spirit and reads to a shopper exactly like what it is.

How to judge an alt text

Whether you wrote it, a freelancer did or a model did, seven questions settle it. The first is the only one that matters; the rest are the usual ways of failing it.

  1. Substitution. Delete the image, put the alt text in its place, read the page. Does it still do its job?
  2. Job match. Is it describing a picture where it should be naming an action? Thumbnails, zoom triggers, swatches and image buttons are controls.
  3. Duplication. Does the sentence before or after already say this? Shorten it, or empty it.
  4. Padding. Does it open with “image of”, “photo of”, “picture of”? A screen reader has already said it is an image.
  5. Provenance. Is it a filename, a SKU, a sequence number or a keyword list? All four are F30 failures.
  6. Length. More than a sentence or so usually means a complex image whose content belongs in the page.
  7. Silence on purpose. If it is empty, is that a decision you would defend — and is the title attribute empty or absent too?

Run those out loud on one real product page and you will know more about your store’s alt text than any report can tell you.

How we draft alt text, and where we stop

Alt text is one of the ten fix families our engine works on, and it is the one where the industry over-claims hardest, so here is the whole mechanism with the limits attached.

Detection. Missing and empty text alternatives are found by three rules — image-alt, input-image-alt and role-img-alt — and criterion 1.1.1 is checked by more than one independent scan engine, with agreement between them reported as a confidence signal. What those rules cannot flag is alt text that is present and useless: alt="product image" satisfies all three, which is why the Media Library worklist above starts from the library rather than from a scan report. Across the whole surface we track 55 criteria: 39 have some automated coverage and 16 are audit-only. We publish that split rather than implying the automatable set is the standard.

Drafting. For each flagged image, a vision model sees the image plus the context that decides its job: the enclosing link, any caption, the nearby heading and body text, the filename, the page title. It classifies the image first — decorative, informative or functional — and only then writes, because as this guide has laboured, the classification decides the answer.

Auditing. A second, independent and deliberately stricter model reviews every draft against objective failure modes only: is the image type right, does the text match what is in the image, does it pass the hygiene rules above — no “image of”, no filename, sane length, not redundant with adjacent text. It is not asked whether the sentence is elegant, only whether it is wrong.

Routing. That verdict produces a confidence, and the confidence decides what happens next. A clear pass is applied. Doubt — an ambiguous image type, an uncertain decorative call — goes to the human review queue; anything judged inaccurate is flagged for an expert. The bias is deliberately towards review: we would rather hand you a queue than publish a sentence about your product that is not true.

Applying and proving. Accepted alt text is written server-side into the served HTML by the WordPress plugin — never an overlay, never a script relabelling the page after load. The page is then re-scanned with the rule that flagged it, and the decision written to an immutable record you can export.

What we will not claim. Re-validation proves the text alternative is present and passed the audit gate. It does not prove the sentence is good — only someone who knows your products can, which is why the review queue is a first-class part of the product rather than an error state. Images injected by JavaScript after load cannot be fixed server-side; those are reported, not silently dropped. Whether your sale banner should have been real text at all is criterion 1.4.5, audit-only, and no automated system should tell you it has handled that. All of it is documented, evidenced remediation — and what no software can issue is a conformance certification, which comes from qualified humans, for a defined scope and on a defined date. We keep the two apart on purpose.

To see the loop end to end, how it works walks through detect, fix, prove and keep. If you would rather start from evidence, run a free scan and read the images it lists — then open your two best sellers and try the substitution test by hand.

On this subject

The fixes this guide refers to:

The other guides

Sources

Written by the Klarvo Access team. Published 2026-08-04.

FAQ

Questions this raises

Should the product name go in the alt text?

Usually yes, in the words a shopper would actually use — but not the full keyword-stuffed title, and not when the link or heading beside the image already says it. WCAG Technique H2 exists precisely to remove that duplication: combine the image and the text into one link and give the image null alt text.

Is an empty alt attribute ever the right answer on a product page?

Often. Decorative flourishes, background textures and images whose meaning is already carried by the words next to them should have alt="" so assistive technology can ignore them. An empty alt is a decision. A missing alt attribute is not the same thing — W3C notes that when the attribute is left out, some screen readers announce the file name instead.

Can a scanner tell me whether my alt text is any good?

No. Automated rules check whether a text alternative exists, not whether it is accurate or useful. "product image" passes a presence check and tells a shopper nothing. Judging quality needs someone — or something — that has looked at the image and at the page around it.

I fixed the alt text in the Media Library. Why is the old text still on my pages?

WordPress documents that Media Library alt text is the default applied when an image is inserted, and that changing the alt text inside the block afterwards applies only to that page or post. So library edits do not rewrite blocks that already carry their own copy. Images rendered from the attachment at request time do follow the library value.

Does automated alt text put us in the clear?

It removes a real barrier for real customers, which is why we do it. It is not a conformance claim. Automation can apply a fix and prove the fix is live in the served HTML; what automation cannot issue is a conformance certification, which comes only from a qualified human audit, at a stated scope and on a stated date.

Scan the store this applies to.

Three engines, nine probes, your real pages — home, product, populated basket, hydrated checkout. Free, no signup, and the findings are yours whether or not you buy anything.