Reputation: 23
I’m building a custom Web Component that displays live sustainability stats for hotels. The component updates CO₂ savings in real-time, and I want screen readers to announce the updates using an ARIA live region.
The problem is that the screen reader only announces the first update, but ignores all future changes unless I manually re-render the component.
Here’s my Web Component:
class LiveStats extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = `
<div aria-live="polite" id="co2-output">CO₂ Saved: 10kg</div>
`;
}
updateCO2(newValue) {
const output = this.shadowRoot.getElementById("co2-output");
output.textContent = `CO₂ Saved: ${newValue}kg`;
}
}
customElements.define("live-stats", LiveStats);
I expected that calling updateCO2(15)
would trigger a screen reader announcement for the new value.
I also tried forcing a reflow:
output.textContent = "";
setTimeout(() => {
output.textContent = `CO₂ Saved: ${newValue}kg`;
}, 10);
But this doesn’t work reliably.
Are ARIA live regions inside Shadow DOM unreliable, or is there another technique to ensure screen readers consistently read dynamic updates?
UPDATE: The following is some dummy code. The page only has a single Web Component (live-stats) with a simple counter that updates every 3 seconds. The text "Updates: 0" is a placeholder—it doesn’t represent real-world data. The page doesn’t connect to a backend, API, or external database. If anyone can play around with this to figure out why some screen readers struggle with live updates inside Shadow DOM because they don't always detect changes inside the encapsulated component.
<html lang="en"> <head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Live Stats Web Component</title> </head> <body>
<live-stats></live-stats>
<script>
class LiveStats extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = `
<div aria-live="polite" aria-atomic="true" id="counter">Updates: 0</div>
`;
this.count = 0;
}
updateCount() {
const counter = this.shadowRoot.getElementById("counter");
// Force DOM mutation to trigger screen reader announcement
counter.textContent = '\u2060'; // Invisible character
setTimeout(() => {
counter.textContent = `Updates: ${++this.count}`;
}, 50);
}
}
customElements.define("live-stats", LiveStats);
// Instantiate the component and update every 3 seconds
const stats = document.querySelector("live-stats");
setInterval(() => stats.updateCount(), 3000);
</script>
</body> </html>
Upvotes: 1
Views: 89
Reputation: 428
I tried the dummy code with NVDA V2024.4.2.35031 and Chrome V134.0.6998.89 and everything worked, it seems your problem is due to a browser or screen reader issue. As you stated in the comment's the problem was due to the browser being outdated.
Upvotes: -1