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.

Pausing correctly and pausing badly Comparison of pausing the announcement loop while keeping data flowing against pausing the data connection itself. Pausing correctly and pausing badly✓ Pause announcementsThe socket stays connectedTicks are buffered or discarded by policyOne summary when the tab becomes visibleThe visible table is current on return✗ Pause the connectionReconnect cost on every tab switchA gap in the data the user cannot seeA stale table on returnAnnouncements resume before the data does
Pause the speech, not the data — the user still wants current numbers when they return.

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.

What happens when the tab is hidden Timeline showing a feed continuing while the tab is hidden, the backlog it produces, and the resume summary that replaces it. What happens when the tab is hiddenTab hiddenflush loop pausedFeed continuesbuffered, not announcedTab visibleone resume summaryNormal cadencethrottled flushes resumetime across a tab switch →
A screen reader keeps speaking a hidden tab — the backlog is real, not theoretical.

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.

Resuming without a backlog Three-step resume: discard the buffered ticks, recompute from current state, and announce one summary describing the interval. Resuming without a backlogDiscard the bufferthe individual ticks are no longer usefulKeep the latest state, not the historyRecompute the summaryfrom current values, not from the queueOne sentence covering the whole intervalAnnounce once"Resumed. 3 alerts and 240 updates while away."Then return to the normal cadence
The user does not want the ninety announcements they missed — they want to know what changed.

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.

Permalink to "Related"

Back to Real-Time Data Stream Announcements