PLAINFINCH learn by building

Announcing changes

You will make a basket message get read out by screen readers each time it changes on the page.

Why does a screen reader miss changes on the page?

A screen reader reads one thing at a time: whatever has focus, or wherever the user is reading. When you press a button and a script writes a message somewhere else on the page, a sighted visitor sees it out of the corner of their eye. A screen reader user hears nothing. Focus is still on the button, and the reading position has not moved, so the new text sits there unseen until they happen to read down to it.

Try it on the starting page. Press **Add to basket** in the Page view, then open the Screen reader view. The message is in the list of lines, so a person could find it by reading the whole page again. Nothing was announced at the moment it changed. Someone shopping for brake pads would press the button, hear silence, and press it again, and again, ending up with four pairs in the basket.

WCAG 2.2 covers this in 4.1.3 Status Messages, at level AA: when a page shows a status message without moving focus, assistive technology must be told about it. Live regions are how you tell it.

What is a live region?

A live region is an element that the browser and screen reader watch for changes. When text inside it is added, removed or replaced, the screen reader reads the new text out, without moving focus and without the user doing anything. You make one with an attribute: aria-live="polite", aria-live="assertive", or a role that implies one, such as role="status" or role="alert".

The browser passes changes to the accessibility tree as events. The screen reader listens for those events on live regions and queues an announcement. That is why the region itself must be in the page first: the screen reader has to know about the container before it can notice what changes inside it.

The Screen reader view on the right shows announcements as they happen. Press the button after adding role="status", and the message appears as an announced line. Like the rest of that view, it is a model: real screen readers differ in timing, and some read a short pause or a sound first.

<button type="button" id="add">Add to basket</button>
<p id="basket-msg" role="status"></p>

When should you use polite, and when assertive?

A polite region waits. If the screen reader is in the middle of a sentence, it finishes and then reads the update. Most messages belong here: “Brake pads added”, “Saved”, “12 results”, “Copied to clipboard”. role="status" is the usual way to write one, and it also makes the region atomic, so the whole message is read rather than only the part that changed.

An assertive region interrupts. The screen reader stops what it is saying and reads the update straight away. role="alert" works this way. Use it when waiting would cause harm or confusion: a payment failed, the session is about to time out, the form could not be sent. If every update is assertive, users get cut off mid-sentence over and over, and they stop trusting the alerts.

A rule of thumb that holds up well: if the user caused the change and expects it, be polite. If something went wrong, or the page needs them to act now, be assertive.

  • role="status": results counts, added to basket, saved, progress steps
  • role="alert": errors that block the task, time-out warnings, failed payments
  • aria-live="off" or no attribute: everything else

Why must the region exist before the message?

This is the mistake that catches most people, and AI tools make it all the time. A script creates a brand new <p role="status">, puts text in it and adds it to the page. It looks correct, and in many screen readers nothing is said, because the region and its content arrived in the same moment. There was no change inside a watched region, only a new element.

The reliable pattern is to put an empty region in the HTML when the page loads and change its text later. The bird club page in Break It has exactly this bug: its Saved message is created when the button is pressed, and the fix is to move the empty paragraph into the HTML.

role="alert" is a partial exception. Many screen readers announce an alert element when it is added, which is why the Screen reader view here treats a new alert as announced. Even so, an alert that already exists and gets new text works in more places, so keep the container in the page for alerts too.

<!-- in the HTML from the start -->
<p id="signup-msg" role="status"></p>

<script>
  // later, when the form is sent
  document.getElementById('signup-msg').textContent =
    'Thanks. Check your inbox to confirm your email.';
</script>
FigureTop: an empty p id signup-msg role status exists from page load; later its text is set to Thanks. Check your inbox, and it is announced. Bottom: a new status element created with Saved already inside is often not announced.Top: an empty p id signup-msg role status exists from page load; later its text is set to Thanks. Check your inbox, and it is announced. Bottom: a new status element created with Saved already inside is often not announced.
A live region announces changes inside it, so it has to be watched before the message arrives. Put the empty status paragraph in the HTML and only change its text later.

What should a good announcement say?

Write the message for someone who cannot see the page. “Done” is vague when you cannot see what was done. “Brake pads added. Your basket has 2 items.” confirms the action and gives the new state in one breath. Keep it short, because it is spoken over whatever else is happening, and put the most useful words first.

Each change should read as a whole sentence. If you update only a number inside a longer sentence, a polite region without aria-atomic="true" may read just the number: “2”. role="status" sets atomic for you, which is one more reason to prefer it.

Repeating the same text is another trap. Setting a region to the exact text it already holds is not a change, so nothing is announced. The demo avoids this because the count changes each time. On a page where the message can repeat, clear the region first, wait a moment, then write the message again.

Which updates need announcing, and which do not?

Announce results of actions the user cannot otherwise perceive from where they are: an item added to a basket, a filter that changed the number of results, a file that finished uploading, a form saved in the background. Announce errors that appear without a page load, and progress on slow tasks, sparingly.

Do not announce everything that moves. A clock ticking every second, a stock ticker, or a chat that fills with other people’s messages can make a page unusable if each change is read aloud. For a chat or log, role="log" is polite and reads only new entries. For a timer, leave it silent and let people read it when they want.

When focus moves to new content, a live region is the wrong tool. Opening a dialog moves focus into the dialog, and the screen reader reads it there, so it needs no announcement. The next lesson covers that pattern.

How do you test live regions here and in a real screen reader?

In the views on the right, press the button in the Page view, or use the Keyboard view to Tab to it and press Enter. Then look at the Screen reader view for the announced line. Press it a second time and check the count changes. Check my work does the same thing in a fresh copy of your page each time.

With a real screen reader, turn it on, Tab to the button and press it. Listen for the message. Then try the case people forget: press it while the screen reader is still reading the button’s name, and check a polite message waits its turn. If you hear nothing at all, check that the region is in the HTML before the script runs, that it is not inside something hidden, and that the text actually changes.

Automatic checkers cannot test this well, because nothing is wrong with the HTML at rest. The bug only shows up when something happens, which is why the checks here press the button first.

What do AI tools get wrong about live regions?

Generated code often adds aria-live="assertive" to a whole form or a whole results list, so every small change interrupts the user. It creates status elements on the fly and appends them, which many screen readers never announce. It puts role="alert" on a message that is shown on page load, which some screen readers announce and some do not, depending on timing.

It also tends to hide the live region with display: none until there is a message, then show it. A region hidden that way is outside the accessibility tree, so the change may be lost. Keep the region visible and empty instead, or use a visually hidden class if the message should only be heard.

When you review AI output, find every place a script writes text with textContent or innerHTML. For each one, ask whether a screen reader user would notice. If not, add a polite region that is in the page from the start.

Field notes

  • Screen readers do not notice text that changes away from focus.
  • A live region is watched, and changes inside it are read out.
  • A polite region like role="status" waits until the reader is free.
  • The region must be on the page before the message text goes in.

Easy to get wrong

Many screen readers only announce changes inside a region they already know about. Put the empty region in the HTML first and change its text later.

A results count is not urgent. Assertive messages cut the screen reader off mid-sentence. Keep them for errors and warnings that cannot wait.

A live region around a whole form reads out every small change inside it. Point the region at the one message you want heard.

Questions people ask

Is `role="status"` the same as `aria-live="polite"`?

Nearly. role="status" implies aria-live="polite" and aria-atomic="true", and it also tells assistive technology that the element is a status message. Use the role when the content is a status, and aria-live on its own for other regions.

Does the `<output>` element work as a live region?

Yes. <output> has the status role, so browsers treat it as a polite live region. It suits results of a calculation, like a delivery cost that updates when someone changes a postcode.

Can a live region be visually hidden?

Yes, with a visually hidden class that keeps it in the accessibility tree. Avoid display: none, hidden or aria-hidden="true", which remove it entirely, so nothing is announced.

Should errors on a form be announced with an alert?

When a form is submitted and fails, moving focus to an error summary is usually clearer than an alert. For errors that appear while someone types, a polite message linked to the field with aria-describedby works better than interrupting them.

Keep going