Pausing Live Regions When the Tab Is Hidden
Permalink to "Pausing Live Regions When the Tab Is Hidden"A screen reader does not stop speaking when a tab moves to the background. A live data feed left running in a hidden tab keeps writing to its status region, and the user hears a stock ticker over the top of whatever they switched to. This page covers the visibility-driven pause: what to stop, what to keep running, and the single summary that replaces the backlog when the tab comes back.
It extends real-time data stream announcements, and it builds directly on the throttling in throttling high-frequency aria-live updates.
Spec reference
Permalink to "Spec reference"The Page Visibility API exposes document.visibilityState with the values visible and hidden, and fires a visibilitychange event when it changes. Hidden covers a backgrounded tab and a minimised window; it does not cover a window that is merely occluded by another window.
SC 2.2.1 Timing Adjustable (Level A) requires that automatically updating content can be paused, stopped or hidden unless the updating is essential. A visibility-driven pause is not a substitute for the user-facing pause control that criterion requires — it is an addition, handling the case where the user has moved on without pressing anything.
There is no ARIA attribute involved. The pause is application logic around the flush loop, and aria-busy is not the right tool: the region is not being partially written, it is not being written at all.
When to use — and when not to
Permalink to "When to use — and when not to"Apply this to any feed that announces on a cadence: tickers, telemetry, live logs, collaborative presence counts. If the region writes without user action, it should stop when the tab is hidden.
Do not pause discrete, consequential alerts. A session-expiry warning or a failed save should still be announced when the user returns, because those are the messages that matter most after an absence. Route them through the assertive channel, which is exempt from the pause and delivered on resume.
Do not close the data connection. Reconnecting on every tab switch costs latency and, worse, leaves the visible table stale at the moment the user looks at it again.
Annotated code example
Permalink to "Annotated code example"// SC 2.2.1: an automatic pause, in addition to the user-facing control
let paused = false; // user pause
let hidden = false; // visibility pause
let sinceHidden = { updates: 0, alerts: 0 };
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
hidden = true;
sinceHidden = { updates: 0, alerts: 0 };
stopFlushLoop(); // stop announcing; the socket stays open
} else {
hidden = false;
// Discard the backlog — announce ONE summary of the interval
const { updates, alerts } = sinceHidden;
if (updates || alerts) {
announce(`Resumed. ${alerts} alert${alerts === 1 ? '' : 's'} and `
+ `${updates} update${updates === 1 ? '' : 's'} while away.`);
}
buffer.length = 0; // the individual ticks are no longer useful
startFlushLoop();
}
});
function onTick(tick) {
applyToTable(tick); // the VISIBLE table stays current either way
if (hidden) {
sinceHidden.updates++;
if (tick.severity === 'alert') sinceHidden.alerts++;
return; // no buffering for speech while hidden
}
buffer.push(tick); // normal throttled path
}
Applying the tick to the table while hidden is the part that is easy to get wrong in the other direction. Stopping the DOM updates as well saves a little work and guarantees the user sees stale data the instant they return — which is exactly when they are least likely to notice it is stale.
Keyboard & AT behaviour
Permalink to "Keyboard & AT behaviour"| Situation | Expected behaviour | Failure indicator |
|---|---|---|
| Tab hidden | No announcements at all | The reader speaks over another tab |
| Tab restored | One summary of the interval | A burst of buffered announcements |
| Alert while hidden | Counted, then summarised on return | Silently discarded |
| User pause pressed | Announcements stop until resumed | Visibility resume overrides the user’s choice |
That last row matters. If the user pressed the pause control before switching tabs, returning must not restart announcements — the visibility state and the user’s explicit choice are two separate flags, and the user’s wins.
Integration context
Permalink to "Integration context"The visibility pause layers on top of the throttle rather than replacing it. While visible, the flush loop emits one coalesced summary per window as described in the throttling page; while hidden, the loop does not run at all.
It also interacts with the priority tiers from the parent guide. The polite status channel pauses; the assertive alert channel counts what it missed and reports it in the resume summary. Deciding which severities count as alerts is a product question, but it should be an explicit list rather than an accident of how the feed is shaped.
Gotchas
Permalink to "Gotchas"Announcing the backlog. The single worst outcome, and the default if you simply restart a flush loop over a full buffer.
Pausing the socket. Costs a reconnect and leaves stale data visible on return.
Visibility overriding the user pause. Track them separately.
Timers throttled by the browser. Background tabs have their timers heavily throttled, so a flush loop that was not explicitly stopped will fire irregularly rather than not at all — producing announcements at unpredictable intervals. Stop the loop rather than relying on browser throttling.
FAQ
Permalink to "FAQ"Should a live feed keep announcing when the tab is in the background?
No. Screen readers keep speaking regardless of tab visibility, so a background tab with an active feed produces speech over whatever the user is doing in another tab. Pause the announcement loop on visibilitychange, keep the connection open so the data stays current, and announce one summary when the tab becomes visible again.
Should the buffered updates be announced on resume?
Not individually. Ninety buffered announcements delivered at once is worse than the flooding the throttle exists to prevent. Discard the individual ticks, recompute a summary from the current state, and announce that one sentence — “Resumed. 3 alerts and 240 updates while away.”
Does the Page Visibility API cover every case?
It covers tab switching and window minimisation, which are the common ones. It does not cover a window that is simply behind another window on the desktop, because that window is still visible by the specification’s definition. For that case the pause control required by SC 2.2.1 is the fallback, and it should be reachable at all times.
Related
Permalink to "Related"- Real-time data stream announcements — the parent guide and the priority tiers
- Throttling high-frequency aria-live updates — the cadence this pause suspends
- Progressive loading & skeleton states — the other half of a feed’s lifecycle