Hear what your aria-live region would actually announce.
Paste your HTML, trigger an update, and watch the announcement queue: what gets read, in what order, and whether an assertive update interrupts a polite one still waiting. No NVDA, JAWS, or VoiceOver install required for the first pass.
How LiveRegionLab works
- 1
Paste HTML that contains live regions
Paste the markup for your toast container, form error summary, loading status, or any element with aria-live, role="status", or role="alert". A worked toast-and-error example is preloaded so you can see the simulation immediately.
- 2
The markup renders into a sandboxed pane
Your HTML is rendered inside an isolated iframe on your own machine. Nothing is uploaded. The tool then scans the rendered DOM for every live region and resolves its effective politeness, atomic, and relevant settings, showing you exactly how each value was decided.
- 3
Trigger updates on demand
Each detected region gets its own controls: type a new message and hit Replace, Append, or Clear. This fires a real DOM mutation inside the region, exactly like your app would at runtime, but in a controlled sequence you drive.
- 4
A MutationObserver catches the change
A client-side MutationObserver watches every live region for content changes. When one fires, the tool computes the text that would be announced, honouring aria-atomic (whole region vs just the changed node).
- 5
The announcement queue plays out
Polite messages queue behind whatever is currently being spoken. An assertive message interrupts the current announcement and clears the polite queue. You watch this happen live, with a Now speaking panel, a waiting queue, and a full transcript of what was spoken versus interrupted.
- 6
Static findings flag the classic bugs
Alongside the simulation, the tool flags regions that are not empty on load, assertive overuse, aria-live="off", and partial updates that probably need aria-atomic="true".
What the queue statuses mean
Every announcement in the transcript carries a status. Understanding each one tells you exactly why a message was or was not heard.
The announcement currently being read out. Only one message speaks at a time. A polite update that arrives now will wait behind it; an assertive update will cut it off.
Trigger: #toast (polite) → "Saved"
Now speaking: "Saved" ← occupies the channelThe announcement finished reading without being interrupted. This is the outcome you want for every message the user needs to hear in full.
"Saving…" → spoken
"Saved" → spoken (queued, then read after)A polite announcement waiting for the channel to free up. If two polite updates fire in quick succession, the second queues behind the first. It will be read in order once the current message finishes.
Fire "Item 1" and "Item 2" rapidly:
"Item 1" → speaking
"Item 2" → queued (read next)A message that was speaking or queued when an assertive announcement arrived. Assertive clears the channel, so anything polite that had not finished is dropped and never heard. This is why assertive overuse silently loses status messages.
"Loading page 2…" (polite, speaking)
→ assertive error fires
"Loading page 2…" → interrupted (never finished)
"Validation failed" → speakingARIA live region attribute reference
These are the attributes and roles LiveRegionLab detects and simulates. Copy any of them into the tool to see how they behave.
<!-- Explicit aria-live: polite, assertive, or off -->
<div aria-live="polite"></div>
<div aria-live="assertive"></div>
<div aria-live="off"></div>
<!-- Roles with implicit live semantics -->
<div role="status"></div> <!-- polite + atomic -->
<div role="alert"></div> <!-- assertive + atomic -->
<div role="log"></div> <!-- polite -->
<output></output> <!-- polite (implicit) -->
<!-- aria-atomic: read the whole region, or just the change -->
<div aria-live="polite" aria-atomic="true"></div>
<div aria-live="polite" aria-atomic="false"></div>
<!-- aria-relevant: which mutation types count (default: additions text) -->
<div aria-live="polite" aria-relevant="additions text"></div>When both a role and an explicit aria-live are present, the explicit aria-live value decides politeness. role="timer" and role="marquee" are detected but resolve to off by default.
When to use LiveRegionLab
| Scenario |
|---|
| Toast notifications firing rapidly |
| Form error vs saving status |
| Loading state not announced |
| Design system alert component |
| Counter or timer region |
| Pre-audit sanity check |
Frequently Asked Questions
What does LiveRegionLab do?
LiveRegionLab renders your pasted HTML into a sandboxed pane, detects every element with aria-live, role="status", role="alert", role="log", or an <output> tag, and lets you trigger content updates on demand. Each update is fed through a simulated announcement queue that models the documented behaviour of aria-live: polite messages wait their turn, assertive messages interrupt.
It shows what a screen reader would announce, in what order, and which messages get cut off. It also statically flags the mistakes developers make most often, like a live region that already contains text when the page loads.
Does this replace testing with a real screen reader?
No, and it is not meant to. LiveRegionLab models the spec's intended behaviour so you can catch the common mistakes fast, during development, without opening NVDA or VoiceOver every time you tweak a toast message.
What is the difference between aria-live="polite" and aria-live="assertive"?
| polite | assertive | |
|---|---|---|
| When announced | After the current speech finishes | Immediately, interrupting current speech |
| Queues behind other messages | Yes | No, it clears the queue |
| Good for | Status, progress, saved, toast | Errors, urgent warnings |
| Equivalent role | role="status" | role="alert" |
Rule of thumb: default to polite. Only reach for assertive when interrupting the user is justified, such as a form submission error they need to hear before doing anything else. Overusing assertive makes an interface feel like it is shouting over itself.
What is the difference between aria-live and role="alert"?
role="alert" is a shorthand. It implies aria-live="assertive" and aria-atomic="true" automatically, so you do not have to set them yourself. role="status" is the polite equivalent (implicit aria-live="polite" and aria-atomic="true").
Using the role also gives the region a semantic meaning that some assistive tech exposes beyond just the live announcement. If you set both a role and an explicit aria-live, the explicit aria-live value wins for politeness. LiveRegionLab shows you exactly which value it resolved and why.
<!-- These two are equivalent for announcements -->
<div role="alert"></div>
<div aria-live="assertive" aria-atomic="true"></div>How do I fix an aria-live region that isn't announcing anything?
The single most common cause is that the region is not empty when the page loads, or it does not exist in the DOM until the moment you inject content into it. Screen readers register a live region and then watch it for changes. If the region is created and filled in the same tick, there was no prior state to diff against, so nothing is announced.
- Render the empty live region container in your initial HTML, present and empty.
- Update only its text content later, in a separate tick, when you have a message.
- Do not toggle the container with
display:noneand back, some screen readers stop watching hidden regions. - Keep the aria-live attribute on the container that persists, not on the message element you add and remove.
What does aria-atomic do, and when do I need it?
aria-atomic controls how much of the region is read when part of it changes. With aria-atomic="false" (the default for a bare aria-live), only the changed node is announced. With aria-atomic="true", the entire region is read from scratch every time any part of it changes.
You need aria-atomic="true" when the message only makes sense read in full. A timer that reads "5 minutes remaining" should announce the whole phrase, not just the digit that changed. LiveRegionLab flags multi-child regions that update partially without aria-atomic set.
| aria-atomic | What gets announced | Use for |
|---|---|---|
| false (default) | Only the node that changed | Appending log lines, chat messages |
| true | The entire region, re-read in full | Timers, counters, single status phrases |
Is my HTML sent to a server?
No. Everything runs in your browser. The HTML you paste is rendered into a sandboxed iframe on your own machine, mutations are watched with a client-side MutationObserver, and the queue simulation is plain JavaScript. There is no backend, no upload, and no analytics on the markup you paste.