How does dynamic aria-live
content work?
- Inconsistently: You may experience differences in expected behavior
- The screenreader expects content within an element with a
aria-live="polite"
attribute to change - By default, only the content that has changed will be read
- To force the screenreader to read all contents even if it did not change within the element, add
aria-atomic="true"
- Rarely must you use
aria-live="assertive"
as it (is intended to) override every other message from the screenreader
About alerts
- By default an element using
role="alert"
hasaria-live="assertive"
Focus management & consistency
For actual single page apps:
- Focus must be deliberately and consistently placed at the
- top of new page content or
- top of the HTML page
- DO NOT place focus on the first input on page load
Code example
aria-live="polite"
announces only newly injected content after other messages
aria-live="polite" aria-atomic="true"
announces all injected content after other messages
aria-live="assertive"
container announces only newly injected content before other messages
aria-live="assertive" aria-atomic="true"
announces all injected content before other messages
role="alert" aria-atomic="true"
announces all injected content before other messages (like aria-live="assertive")
role="status" aria-atomic="true"
announces all injected content after other messages (like aria-live="polite")
<div class="dynamic-app">
<p><code>aria-live="polite"</code> announces only newly injected content after other messages</p>
<ol class="dynamic-content" aria-live="polite" aria-atomic="false">
</ol>
<button type="button" onclick="showContent()">Update content</button>
</div>
<div class="dynamic-app">
<p><code>aria-live="polite" aria-atomic="true"</code> announces all injected content after other messages</p>
<ol class="dynamic-content" aria-live="polite" aria-atomic="true">
</ol>
<button type="button" onclick="showContent()">Update content</button>
</div>
<div class="dynamic-app">
<p><code>aria-live="assertive"</code> container announces only newly injected content before other messages</p>
<ol class="dynamic-content" aria-live="assertive" aria-atomic="false">
</ol>
<button type="button" onclick="showContent()">Update content</button>
</div>
<div class="dynamic-app">
<p><code>aria-live="assertive" aria-atomic="true"</code> announces all injected content before other messages</p>
<ol class="dynamic-content" aria-live="assertive" aria-atomic="true">
</ol>
<button type="button" onclick="showContent()">Update content</button>
</div>
<div class="dynamic-app">
<p><code>role="alert" aria-atomic="true"</code> announces all injected content before other messages (like aria-live="assertive")</p>
<ol class="dynamic-content" role="alert" aria-atomic="true">
</ol>
<button type="button" onclick="showContent()">Update content</button>
</div>
<div class="dynamic-app">
<p><code>role="status" aria-atomic="true"</code> announces all injected content after other messages (like aria-live="polite")</p>
<ol class="dynamic-content" role="status" aria-atomic="true">
</ol>
<button type="button" onclick="showContent()">Update content</button>
</div>
<template>
<li>Injected content</li>
</template>
<script>
function showContent() {
let temp = document.getElementsByTagName("template")[0];
let clone = temp.content.cloneNode(true);
event.target.parentNode.querySelector('.dynamic-content').appendChild(clone);
}
</script>