# Image uploads that never ask for alt text

Source: https://easeweb.dev/learn/alt-text-prompts
Topics: Page structure > Editors and uploads ask for alt text and keep headings (EN 301 549 11.8.2, 11.8.3, 11.8.4, 11.8.5; Section 508 504)
The fix: Either ask for a description as each photo is added, in a dialog with a labelled field and an option to mark it as decorative, or check every photo before the listing is published and ask for each missing description there. Never fill the alt text with the file name.
Test with: keyboard, screen reader
References: https://www.etsi.org/deliver/etsi_en/301500_301599/301549/03.02.01_60/en_301549v030201p.pdf https://www.access-board.gov/ict/ https://www.w3.org/TR/ATAG20/ https://www.w3.org/WAI/tutorials/images/decision-tree/ https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

## What happens

Makers Market is a marketplace where small makers list what they sell. A seller lists a white enamel mug with a navy rim. The listing form has a title and an Add a photo button; the seller picks the photo from their phone, sees it appear in the listing, and publishes.

The form never asked what the photo shows. It wrote the file name, IMG_2041.jpg, as the image's alt text, because an image needs some alt text and that was the only text it had. A shopper using a screen reader reaches the photo and hears "IMG_2041.jpg, image". The seller described the mug carefully for the eye, in the photo, and nothing of that reaches the shopper.

## Why it fails

The listing form is an authoring tool: something sellers use to make web content. EN 301 549 11.8.2 asks that an authoring tool "enable and guide the production of content" that meets the web requirements, and Section 508 504.3 asks for a mode that prompts authors to make content that conforms to WCAG. A form that never asks for a description does neither: it gives the seller no place to describe the photo, and it doesn't guide them to.

Filling in the file name makes it worse. The published photo now fails WCAG 1.1.1, since "IMG_2041.jpg" doesn't serve the purpose of the photo, but it has an alt attribute, so it looks finished to a check for the attribute and to the seller. Section 508 504.2 also asks that authors can override what is required for accessibility. Here, that means a seller must be able to mark a photo as decorative, so the override is the author's choice and not the tool's guess.

## Who is affected

The failure reaches shoppers, not the seller who used the form, and every listing the form publishes repeats it. On a marketplace with thousands of sellers, one missing question in the form becomes thousands of photos that say nothing, which no single seller can fix.

## What automation and AI miss

axe checks that each image has an alt attribute, and this one has, so it passes. ANDI flags alt text that contains a file name, and this audit passes its alert on against the published image, as an image problem; it has no way to see that the form which made the image is the cause. Where a page the audit is given has a form that uploads images and has no field to describe them, it sends a review lead, since the form may ask on a later step it can't see. Most authoring tools sit behind a sign-in, so check the seller's own pages by hand.

AI image descriptions can fill the gap, and can mislead. A generated description of the photo ("a white cup on a table") says what is in the picture but not what a buyer needs: that it is enamel, holds 350 ml, and has a navy rim. Used as a suggestion the seller reads and corrects, it helps; published without being read, it is another default text.

## Before: a photo published with its file name

```html
<form action="/listings" method="post">
  <label for="title">Title</label>
  <input id="title" name="title">
  <label for="photo">Add a photo</label>
  <input id="photo" type="file" accept="image/*">
  <div id="photos"></div>
  <button type="submit">Publish listing</button>
</form>
```

```js
const photo = document.getElementById('photo');
const photos = document.getElementById('photos');

photo.addEventListener('change', async () => {
  const file = photo.files[0];
  const img = new Image();
  img.src = await upload(file);
  img.alt = file.name;
  photos.append(img);
});
```

The form adds the photo the moment it is chosen and fills its alt text with the file name, so the seller is never asked what it shows and the published listing reads "IMG_2041.jpg".

## After: ask for a description when the photo is added

```html
<form action="/listings" method="post">
  <label for="title">Title</label>
  <input id="title" name="title">
  <label for="photo">Add a photo</label>
  <input id="photo" type="file" accept="image/*">
  <div id="photos"></div>
  <button type="submit">Publish listing</button>
</form>

<dialog id="describe" aria-labelledby="describe-title">
  <form method="dialog">
    <h2 id="describe-title">Describe this photo</h2>
    <img id="preview" alt="">
    <label for="photo-alt">What does the photo show?</label>
    <p id="photo-alt-hint">Shoppers who can't see the photo hear this instead. Say what a buyer
      would want to know: colour, size, what it's made of.</p>
    <textarea id="photo-alt" aria-describedby="photo-alt-hint"></textarea>
    <p id="photo-alt-error" hidden>Describe the photo, or tick the box below.</p>
    <label><input type="checkbox" id="decorative"> It only decorates the listing: nothing to describe</label>
    <button value="add">Add photo</button>
    <button value="cancel">Cancel</button>
  </form>
</dialog>
```

```js
const photo = document.getElementById('photo');
const photos = document.getElementById('photos');
const describe = document.getElementById('describe');
const text = document.getElementById('photo-alt');
const error = document.getElementById('photo-alt-error');
const decorative = document.getElementById('decorative');

photo.addEventListener('change', () => {
  document.getElementById('preview').src = URL.createObjectURL(photo.files[0]);
  text.value = '';
  decorative.checked = false;
  error.hidden = true;
  text.removeAttribute('aria-invalid');
  text.setAttribute('aria-describedby', 'photo-alt-hint');
  describe.returnValue = '';
  describe.showModal();
});

describe.querySelector('form').addEventListener('submit', (event) => {
  if (event.submitter?.value !== 'add' || text.value.trim() || decorative.checked) return;
  event.preventDefault();
  error.hidden = false;
  text.setAttribute('aria-invalid', 'true');
  text.setAttribute('aria-describedby', 'photo-alt-hint photo-alt-error');
  text.focus();
});

describe.addEventListener('close', async () => {
  if (describe.returnValue !== 'add') return;
  const img = new Image();
  img.src = await upload(photo.files[0]);
  img.alt = decorative.checked ? '' : text.value.trim();
  photos.append(img);
});
```

The seller is asked what the photo shows at the moment they know it best, with a hint that says who the words are for and what to put in them. Adding the photo with neither a description nor the box ticked says what to do, beside the field, rather than adding it with no alt text. The box is the seller's override: a photo marked as decorative is published with an empty alt, so screen readers skip it.

## After: check the photos before the listing is published

```html
<form action="/listings" method="post">
  <label for="title">Title</label>
  <input id="title" name="title">
  <label for="photo">Add a photo</label>
  <input id="photo" type="file" accept="image/*">
  <div id="photos"></div>
  <section id="check" hidden aria-labelledby="check-title">
    <h2 id="check-title" tabindex="-1">Describe these photos before publishing</h2>
    <p>Shoppers who can't see the photos hear these words instead.</p>
    <div id="check-list"></div>
  </section>
  <button type="submit">Publish listing</button>
</form>

<template id="check-row">
  <div class="check-row">
    <img alt="">
    <label><span></span> <textarea></textarea></label>
    <label><input type="checkbox"> It only decorates the listing: nothing to describe</label>
  </div>
</template>
```

```js
const photo = document.getElementById('photo');
const photos = document.getElementById('photos');
const check = document.getElementById('check');

photo.addEventListener('change', async () => {
  const img = new Image();
  img.src = await upload(photo.files[0]);
  photos.append(img); // no alt yet: the check before publishing asks for one
});

photo.form.addEventListener('submit', (event) => {
  // Take what the seller wrote in the check, when it is showing.
  for (const row of check.querySelectorAll('.check-row')) {
    const words = row.querySelector('textarea').value.trim();
    if (words || row.querySelector('input').checked) row.img.alt = words;
  }
  const missing = [...photos.querySelectorAll('img:not([alt])')];
  if (!missing.length) return;
  event.preventDefault();
  document.getElementById('check-list').replaceChildren(...missing.map((img, i) => {
    const row = document.getElementById('check-row').content.firstElementChild.cloneNode(true);
    row.img = img;
    row.querySelector('img').src = img.src;
    row.querySelector('label span').textContent = `What does photo ${i + 1} show?`;
    return row;
  }));
  check.hidden = false;
  document.getElementById('check-title').focus();
});
```

The photo goes in at once, with no alt attribute, and Publish stops to list every photo still without a description, each with a field to write one and the same decorative option. Focus moves to the check's heading, so a screen reader user hears what is asked. Because it checks the listing rather than the button, it catches photos that arrive any way: dragged in, pasted, or brought over from a bulk import.

## Which option to use

Both pass. Asking when the photo is added gets the description while the seller is looking at the photo, and suits a form where photos only come in through the one button. Checking before publishing catches photos however they arrive, and lets a seller add several photos quickly and describe them together, but it asks later, when they are thinking about publishing. A tool with more than one way to add photos does best with both: the dialog for the button, the check for everything else.

## Implementation decisions

Leave the alt attribute off until the author has answered. A missing alt is something a check can find; a file name, the listing title or the word "photo" looks finished and hides the gap. Publish an empty alt only when the author chose it. Save the description with the photo on the server, as a field of its own, so every page that shows the photo uses it.

Words in the field matter as much as the field. A label that asks "What does the photo show?" and a hint that says who it is for get better descriptions than a field called "Alt text", which many sellers won't know. Don't cap it at a few characters; a sentence is usually right, and anything longer, such as a size chart, belongs in the listing's text, as [missing alt text on images that carry information](/learn/informative-images) explains.

An AI description can go into the field as a draft, marked as suggested, for the seller to read and change before adding the photo. Never publish one the seller hasn't seen, and never use it to skip the question.

Every way into the tool needs the same question:

- A photo dropped onto the page or pasted in arrives without the change event the dialog listens for, so drop and paste need the dialog, or the check, too.
- A bulk import from a spreadsheet or another shop needs a column for descriptions, and flags the rows without one.
- A photo added from the phone app needs the same field in the app.
- A seller needs to be able to edit a description later, from the photo in the listing, not only when it is first added.

The description step is part of the tool, so it has to work for authors with disabilities too. A seller who is blind may add photos a friend took; the dialog needs its name and focus moved into it and back, as [modals that leave keyboard focus behind them](/learn/modal-focus-management) explains, and an error tied to the field only while it shows.

## Verify the fix

1. Add a photo each way the tool allows: the button, drag and drop, paste and any import. Confirm each one leads to a request for a description before the listing can be published.
2. Publish a listing with a described photo and read it with a screen reader: the photo is read as the description, never as a file name.
3. Mark a photo as decorative, publish, and confirm the published image has `alt=""` and is skipped by the screen reader.
4. Go through the description step with only a keyboard, and again with a screen reader: the field's label and hint are read, and an empty description gives an error that says what to do.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after any change to how photos are added.

## Limits

This guide covers how an authoring tool asks for image descriptions. The same clauses ask more of a tool than this. A text editor must let authors make real headings and lists rather than large bold text, as [headings chosen by size, not by structure](/learn/heading-structure) shows from the page's side, and keep them when text is pasted in or converted (EN 301 549 11.8.3, Section 508 504.2.1). Where the tool checks content, it must suggest a repair (11.8.4), and where it offers templates, at least one must be accessible and said to be (11.8.5). Whether the tool's own screens work for authors with disabilities is a separate question, which WCAG and ATAG 2.0 Part A cover. Test with your sellers, and with the shoppers who will read their listings.

---

Get this guide in your coding agent: MCP https://easeweb.dev/mcp (setup at https://easeweb.dev/services/developers)
