Skip to content
easeweb
Search topics

Skeleton screens that screen readers never announce

Grey placeholder cards stand in for products while they load and are swapped for them in silence, so a screen reader hears neither the wait nor the arrival.

WCAG 2.2
SC 4.1.3AA· new in 2.1
Required by
EN 301 549 (EU)
Broken <div class="skeleton line"></div>
The accessibility problem illustrated: No vision:Is there nothing under this heading?
01

The problem

Screen reader users find nothing under the heading while the cards load, and are never told when the products arrive

02

Try it

Read the new arrivals while the grey cards show, then wait and listen

  • Broken 1: press Read the page while the grey cards show. The panel shows what a screen reader reads from the top, then anything it says by itself. Then wait for the products.

  • Broken 2: the same cards, with aria-busy and aria-live on every placeholder. Read the page again, and wait.

  • Apply the fix. Read the page while the cards load, then wait. Load again starts over.

Broken Can you fix this?

New arrivals

Free returns within 30 days.

Screen reader says:

(Press Read the page while the grey cards show)

A recreated demo. The broken version is intentionally inaccessible.

03

The fix

Say that the cards are loading, and when they arrive

Before

Grey placeholder cards

<h2>New arrivals</h2><div id="arrivals" class="cards">  <div class="card">    <div class="skeleton photo"></div>    <div class="skeleton line"></div>    <div class="skeleton line short"></div>  </div>  …</div><p>Free returns within 30 days.</p>
What is wrong2 points
  • The grey cards are empty divs, so a screen reader goes from the heading straight to "Free returns within 30 days.", and nothing says the products are loading.
  • The products are written into a div that is not a live region, so their arrival is never announced.
After
FixA status line, with the placeholders hidden
<h2>New arrivals</h2><p id="arrivals-status" role="status" class="visually-hidden"></p><div id="arrivals" class="cards" aria-busy="true">  <div class="card" aria-hidden="true">    <div class="skeleton photo"></div>    <div class="skeleton line"></div>    <div class="skeleton line short"></div>  </div>  …</div><p>Free returns within 30 days.</p>
How it is fixed6 steps
  1. Add an empty p with role="status" under the heading, outside the grid, so the browser is already watching it when the script writes to it. The visually-hidden class keeps it out of the design: on screen, the grey cards already show the wait.
  2. Give each placeholder card aria-hidden="true". The cards have nothing to say, and any live or busy attributes a library puts on its placeholders go quiet with them.
  3. Mark the grid aria-busy="true" while it fills. A screen reader that honours it can hold back a half-built grid; it says nothing by itself.
  4. When the request starts, write "Loading new arrivals…" into the status line. The screen reader reads it once it has finished what it is saying, and reading the section now gives the heading, then that line.
  5. Once every product is in the grid, write the count: "3 new arrivals loaded." If the request fails, clear the grey cards and write "New arrivals could not be loaded." instead.
  6. Set aria-busy back to false either way.

This fixes both Befores and passes 4.1.3: the wait and the arrival are each put into words in a status region, without focus moving. The message is written once, when the grid is complete, not once per card.

Easily confused
04

Design ideas

Put the wait into words, and the arrival too

Four ways to draw the New arrivals cards while they load. Each sample loads when it scrolls into view, and Load again runs it again. Three pass because the wait and its end are said in words, once; one writes “Loading…” on the cards and still fails.

In the design file

  • Write the loading message and the one that replaces it, word for word, such as “Loading new arrivals…” and “3 new arrivals”.
  • Draw the failed load as a sentence that says what happened, with a Try again button where the grey cards were.
  • Mark the grey cards as decoration, with no words inside them.
  • Write how many times the grey cards pulse and how long each pulse takes, under five seconds in all, and draw them still for a longer wait.

Passes

  • New arrivals

    A loading line above the cards A quiet line under the heading says “Loading new arrivals…”, then “3 new arrivals”, in #4b5563 on white (7.56:1). It is the line a screen reader reads too, so sighted people and screen reader users get the same words, and a slow load no longer looks the same as a stalled one.
  • New arrivals

    Cards that fill in one by one Each grey card turns into its product as it arrives, 0.4 seconds apart, fading in over 0.2 seconds, so the page feels quick. The message waits for the last card and is said once, “3 new arrivals loaded.”, not once per card.
  • New arrivals

    A failed load drawn as a message When the request fails, the grey cards give way to “Couldn’t load new arrivals.” and a Try again button (white on #1d4ed8, 6.70:1), and the same sentence is said once. Grey cards left on screen would tell sighted people the products are still coming, and tell no one else anything.

Fails

  • New arrivals

    Loading…
    Loading…
    Loading…
    “Loading…” written on each cardWCAG 4.1.3 The word is on screen, three times, in #4b5563 on the grey (6.10:1), but it belongs to the cards and goes away with them: a screen reader that comes across it reads it once per card, and nothing says the products arrived. One line outside the cards says the same thing once.
05

Verify the fix

4 checks, no mouse.

Test with: Screen reader

0 of 4 checked

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the loading component or the data layer changes.

How screen readers handle role="status" and aria-busy="true"

Limits

Live regions and aria-busy behave differently across screen readers and browsers. A message written while the page itself is still loading may be read late, or not at all, if the screen reader starts reading the page at the same moment; the status line stays in the reading order, so someone who reaches the section later still finds "Loading new arrivals…" or the count. This guide covers placeholders that stand in for one section on one page. A spinner or a loading message after a button press is covered in loading spinners that screen readers never announce, a list that loads more as it scrolls in infinite scroll that moves the footer away as Tab nears it, and a whole new view in single-page app route changes that are never announced. Test your own pages with the screen readers your users use.

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.

EaseWeb service

Fixed it? Keep it from coming back.

I turn guides like this one into rules your coding agents follow and checks your CI runs. Then I retest by hand with a keyboard and screen readers, periodically.

See the service