# aria-label on divs and spans that screen readers skip

Source: https://easeweb.dev/learn/aria-label-on-generic
Topics: Images and media > Text alternatives sit on elements that can carry them (WCAG 1.1.1, 4.1.2)
The fix: Take the words off the element that can't hold them. Either give the element a role that can be named, such as role="img" for a group of stars, or put the words inside it as visually hidden text, or write them as visible text beside the graphic and hide the graphic with aria-hidden="true".
Test with: screen reader
References: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-label https://www.w3.org/TR/wai-aria-1.2/#namefromprohibited https://www.w3.org/TR/using-aria/ https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html https://dequeuniversity.com/rules/axe/4.10/aria-prohibited-attr

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

A shop's listing page shows two products as cards. Each card has the product name as a heading, a row of five stars with some of them filled, and, on one card, a small truck icon that means delivery is free. A sighted shopper sees at a glance that the apron is rated 4 out of 5 and ships for free, and the tote is rated 3. A screen reader in its reading mode reads "Linen apron, heading level 3", then "Canvas tote, heading level 3", and nothing in between. Another screen reader on the same page reads "Rated 4 out of 5, Free delivery". The developer tested with the second one, so the page looked finished.

## Why it fails

The stars and the truck are drawn with CSS, so the elements that hold them are empty. To give them words, each one got an `aria-label`: `<span class="stars" aria-label="Rated 4 out of 5">` and `<span class="icon-truck" aria-label="Free delivery">`. A `span` with no role has the role `generic`, and ARIA prohibits a name on that role. Browsers and screen readers are free to drop the label, and several common ones do in reading mode, because there is nothing to name: it is not a control, an image or a region. So the rating and the offer have no text alternative that reliably reaches assistive technology, which fails 1.1.1. axe reports the attribute itself under 4.1.2, because the name it was meant to provide is not exposed.

## Who is affected

The failure depends on the screen reader and how it is used: one may read the label in one mode and skip it in another. That is what makes it easy to miss. A page that works only with the screen reader the developer happens to use doesn't work.

## What automation and AI miss

axe's `aria-prohibited-attr` flags an `aria-label` on a `div` or `span` with no role, so the attribute is found. Many teams ignore the result, because when they check with one screen reader, the label is read. The scanner can't tell whether the words matter: an `aria-label` repeating visible text is harmless clutter, while one carrying a rating is lost information. A quick AI fix often adds `role="img"` to any element with a label, including ones that contain text or links, which then disappear inside the image. Another often moves the label into a `title` attribute, which most screen readers don't read either. Whether each label carries something people need, and whether it now reaches them, is for a person to check.

## Before: labels on spans that can't be named

```html
<ul class="products">
  <li class="card">
    <h3>Linen apron</h3>
    <span class="stars stars-4" aria-label="Rated 4 out of 5"></span>
    <span class="icon-truck" aria-label="Free delivery"></span>
  </li>
  <li class="card">
    <h3>Canvas tote</h3>
    <span class="stars stars-3" aria-label="Rated 3 out of 5"></span>
  </li>
</ul>
```

The stars and the truck are background images drawn by CSS, so each span is empty:

```css
.stars {
  display: inline-block;
  width: 80px;
  height: 16px;
  background: url("stars.svg") no-repeat;
}
.stars-4 { background-position: 0 -64px; }
.stars-3 { background-position: 0 -48px; }
.icon-truck {
  display: inline-block;
  width: 16px;
  height: 16px;
  background: url("truck.svg") no-repeat;
}
```

The spans are empty and have no role, so their labels may never be read, and the rating and the delivery offer are lost for many screen reader users.

## After: give each graphic a role that can be named

```html
<ul class="products">
  <li class="card">
    <h3>Linen apron</h3>
    <span class="stars stars-4" role="img" aria-label="Rated 4 out of 5"></span>
    <span class="icon-truck" role="img" aria-label="Free delivery"></span>
  </li>
  <li class="card">
    <h3>Canvas tote</h3>
    <span class="stars stars-3" role="img" aria-label="Rated 3 out of 5"></span>
  </li>
</ul>
```

`role="img"` makes each span one image, and an image can be named, so screen readers read "Rated 4 out of 5, image" and "Free delivery, image". It is the smallest change: one attribute on each element. It works only because the spans hold nothing but a picture. `role="img"` hides everything inside it, so never put it on an element that contains text or a link. The words still exist only for assistive technology, and the truck still means nothing to a sighted shopper who doesn't know it.

## After: visually hidden text in place of the label

```html
<ul class="products">
  <li class="card">
    <h3>Linen apron</h3>
    <span class="stars stars-4"><span class="visually-hidden">Rated 4 out of 5</span></span>
    <span class="icon-truck"><span class="visually-hidden">Free delivery</span></span>
  </li>
  <li class="card">
    <h3>Canvas tote</h3>
    <span class="stars stars-3"><span class="visually-hidden">Rated 3 out of 5</span></span>
  </li>
</ul>
```

```css
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
```

The words are now text in the page. Every screen reader reads text, in every mode, so the cards read "Linen apron, heading level 3, Rated 4 out of 5, Free delivery". Page translation translates the words, and nothing is announced as an image. The design is unchanged, so the truck still has to be recognised by sighted shoppers.

## After: write the rating and delivery as text

```html
<ul class="products">
  <li class="card">
    <h3>Linen apron</h3>
    <p class="rating"><span class="stars stars-4" aria-hidden="true"></span> 4 out of 5</p>
    <p class="delivery"><span class="icon-truck" aria-hidden="true"></span> Free delivery</p>
  </li>
  <li class="card">
    <h3>Canvas tote</h3>
    <p class="rating"><span class="stars stars-3" aria-hidden="true"></span> 3 out of 5</p>
  </li>
</ul>
```

The rating and the offer are written beside the graphics, so the stars and the truck are decorative and get `aria-hidden="true"`. Screen readers read "4 out of 5" and "Free delivery", sighted shoppers who don't know the truck can read the words, and the text enlarges and translates with the page. It needs no ARIA at all, only room on the card for a few words.

## Which option to use

All three put the rating and the delivery offer where screen readers read them, so all three meet 1.1.1. `role="img"` is the smallest change, but its words reach only assistive technology, and some page translators skip `aria-label`. Hidden text reads in every screen reader and translates, but it too leaves sighted people with only the icon. Writing the words on the card is plain HTML, reaches everyone who reads the page, and leaves nothing for ARIA to carry. Use it. Keep the other two for a place with truly no room for words, and then prefer hidden text, which needs no role and is translated.

## Implementation decisions

**Which elements can take a name.** `aria-label` works on elements with a role that can be named: controls such as buttons, links and fields, landmarks such as `nav`, images, iframes, dialogs, tables and groups. ARIA prohibits a name on these roles, among others:

- `generic`, the role of a `div` or `span` with no other role;
- `paragraph`, the role of `p`;
- `presentation` and `none`, which remove an element's role on purpose;
- text-level roles such as `strong`, `emphasis`, `code`, `time`, `mark`, `deletion`, `insertion`, `subscript`, `superscript`, `term` and `definition`, and `caption`.

`aria-labelledby` is prohibited on the same roles, so pointing it at hidden text doesn't help.

**Text that should be read differently.** A label is often added to make text read differently, for example `<time aria-label="3 October 2026">3 Oct</time>` or `<span aria-label="12 items">12</span>`. Neither element can be named. Write the fuller words as visible text, or add visually hidden text beside the short form and hide the short form with `aria-hidden="true"`. Screen readers already read short dates and numbers well, so check that the change is needed before making it.

**`title` and `aria-describedby` are not workarounds.** A `title` attribute is read by some screen readers on some elements and shown only on mouse hover. `aria-describedby` is allowed on any element, but screen readers read descriptions mainly when a control takes focus, so a description on a span is easy to miss.

**`role="img"` hides its content.** Everything inside an element with `role="img"` is treated as part of the picture and not read. Use it only on an element that holds a graphic and nothing else, and put any count or link outside it.

**Component props.** Many components take a `label` prop and pass it to their outer `div` as `aria-label`. Check what a component renders: a label on a wrapper with no role is lost. In a shared icon or rating component, set `role="img"` whenever a label is passed, or render the label as hidden text.

## Verify the fix

1. In the browser's accessibility tree, check each rating and delivery icon: it is an image with a name that says the rating or the offer, or its words are text in the card. Run axe and confirm `aria-prohibited-attr` reports nothing on the page.
2. Read each card from its heading with two different screen readers, such as NVDA with Firefox or Chrome on Windows and VoiceOver with Safari on a Mac or iPhone, in their reading mode. The rating and the delivery offer should be read once, in words, in both.
3. Ask someone who can see the cards to compare what they see with what is read: every star rating and icon is also said, and the words give its meaning, not its shape. Where the words are not visible, also check that the people who use the page know what the truck means without being told.
4. Turn on the browser's page translation and check that the words read or shown for the rating and delivery are translated. With `role="img"` and `aria-label`, note whether your translator handles them.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat whenever a shared component that renders labels changes.

## Limits

This is a starting pattern, not a guarantee for every application. Class names are placeholders, and the stars and truck stand for any graphic drawn with CSS or an icon font. Screen readers change how they treat prohibited names between versions, and some read them in one mode but not another, so a pass with one screen reader proves little. Whether a label carries something people need, or only repeats visible text, depends on the page and needs a person to judge. Test the complete journey with your target assistive technology.
