Time limits & auto-updating content

People read, type, and think at very different speeds. A keyboard-only user navigating a long checkout, a screen reader user listening field by field, someone re-reading a sentence three times, or a person who stepped away to find a card number all need one thing the interface often denies them: time, and a way to control it. When a session expires silently, a carousel rotates faster than someone can read a slide, or a feed reloads and yanks the cursor back to the top, the content moves out from under the user — and the task fails through no fault of their own.

This lesson works through three of the most common timing failures and the small, standard controls that fix them. The principle behind all three is the same: don’t make time a hidden requirement. Warn before it runs out, let people pause what moves, and never move someone’s focus without their say-so.

What you’ll learn

How to warn users before a session time limit and let them extend it without losing work; how to give an auto-advancing carousel a real pause, stop, or hide control; and how to update content that refreshes on its own without moving the user’s focus or reading position. Each fix maps to WCAG 2.2.1 Timing Adjustable or 2.2.2 Pause, Stop, Hide.

Standards this lesson maps to
Standard Criterion Level What it requires
WCAG 2.2 2.2.1 Timing Adjustable A For each time limit, the user can turn it off, adjust it, or extend it (with a warning and at least 20 seconds to respond), unless the limit is essential.
WCAG 2.2 2.2.2 Pause, Stop, Hide A Moving, blinking, scrolling, or auto-updating content that starts automatically and lasts more than 5 seconds can be paused, stopped, or hidden by the user.
WCAG 2.2 2.2.4 Interruptions AAA Interruptions such as updates can be postponed or suppressed, except in a real emergency.
WCAG 2.2 3.2.5 Change on Request AAA Changes of context are initiated only by the user, or a way to turn them off is available.
EN 301 549 9.2.2.1 / 9.2.2.2 (incorporates WCAG) European harmonised standard; references the WCAG A/AA set including the timing criteria.
Section 508 502.3 / 504 (incorporates WCAG A & AA) US federal ICT must meet WCAG 2.0 Level A and AA, including 2.2.1 and 2.2.2.
ADA Title II WCAG 2.1 AA (DOJ rule) AA US state/local government web content must conform to WCAG 2.1 AA, which includes these Level A timing criteria.

The three problems we’ll fix

Each card below isolates one common timing defect. For every issue you get a plain-language statement of the problem, a Bad example (shown as escaped, non-running code so nothing on this page moves or expires), a Good example, the copyable Code, and an ordered fix.

Session timeout with no warning or way to extend

WCAG 2.2 · 2.2.1 A EN 301 549 Section 508 ADA Title II

A fixed session or inactivity timeout that logs the user out — or discards a half-finished form — with no warning is a hidden time limit. Someone reading carefully, using a screen reader, or pausing to find information will routinely exceed it and lose their work. Timing Adjustable requires that, before the limit is reached, the user is warned and given a simple action to extend it, with at least 20 seconds to respond, and that they can extend it at least ten times. The only exceptions are limits that are truly essential, such as a timed auction or a security token that cannot be extended without breaking the transaction.

Bad

A timer silently ends the session and throws the user out. There is no warning, no countdown, and no way to ask for more time — the user discovers the limit only after losing their work.

bad-session-timeout.js
<!-- After 15 minutes the user is logged out with no notice -->
<script>
  setTimeout(function () {
    window.location = "/logout?reason=timeout";
  }, 15 * 60 * 1000);
</script>

Good

A warning dialog appears well before the limit, announces the remaining time, and offers a clear Stay signed in button. The user gets far more than 20 seconds to respond, and choosing to extend resets the timer without losing data.

good-timeout-warning.html
<div role="alertdialog" aria-modal="true"
     aria-labelledby="to-title" aria-describedby="to-desc">
  <h2 id="to-title">Your session is about to expire</h2>
  <p id="to-desc">
    For your security you will be signed out in
    <span aria-live="polite">2 minutes</span>.
    Do you want to stay signed in?
  </p>
  <button type="button" id="stay">Stay signed in</button>
  <button type="button" id="logout">Sign out now</button>
</div>

Code

Show the warning ahead of the deadline, give the user time to act, and reset the countdown when they choose to extend. Move focus to the dialog so it is announced, and only redirect if no response comes after the grace period.

timeout-controller.js
const LIMIT = 15 * 60 * 1000;   // total session
const WARN_AT = 2 * 60 * 1000;  // warn 2 min before end
let warnTimer, endTimer;

function start() {
  clearTimeout(warnTimer); clearTimeout(endTimer);
  warnTimer = setTimeout(showWarning, LIMIT - WARN_AT);
  endTimer  = setTimeout(signOut, LIMIT);
}
function showWarning() {
  dialog.hidden = false;
  dialog.focus();              // announce to AT
}
stayBtn.addEventListener("click", () => {
  dialog.hidden = true;
  start();                     // extend: reset both timers
});
start();

How to fix

  1. Warn the user before the limit is reached, not after — show a dialog with the time remaining.
  2. Give at least 20 seconds to respond, and let the user extend the session at least ten times.
  3. Make extending a single, obvious action (a Stay signed in button) that resets the timer and preserves entered data.
  4. Move focus to the warning dialog and use role="alertdialog" so screen reader users are told immediately.
  5. Drop the time limit entirely where you can; reserve fixed limits for cases that are genuinely essential, such as a security token or live auction.

Content that auto-refreshes and moves focus

WCAG 2.2 · 2.2.2 A EN 301 549 Section 508 ADA Title II

A live feed, news ticker, or dashboard that reloads on a timer is auto-updating content under 2.2.2 — the user must be able to pause, stop, or hide it. It gets worse when the refresh re-renders the region and moves focus: a keyboard user is thrown back to the top, a screen reader user loses their place mid-sentence, and anything they were typing or reading vanishes. New content should be added without disturbing the user’s focus or scroll position, announced politely so it doesn’t interrupt, and ideally surfaced through a control the user triggers (“3 new posts — show”).

Bad

The whole feed is replaced on a timer. Replacing innerHTML destroys the user’s focus and reading position, and a full reload throws them to the top of the page with no control to stop it.

bad-autorefresh.html
<!-- Hard reload every 30s — focus and scroll are lost -->
<meta http-equiv="refresh" content="30">

<!-- or, just as bad, wiping the region on a timer: -->
<script>
  setInterval(async () => {
    feed.innerHTML = await fetchLatest(); // destroys focus
  }, 30000);
</script>

Good

New items are inserted above the existing list without touching focus, and a polite live region announces how many arrived. A control lets the user pause the updates whenever they want.

good-autorefresh.html
<div class="feed">
  <button type="button" id="feed-toggle" aria-pressed="false">
    Pause live updates
  </button>
  <p class="sr-only" role="status" aria-live="polite" id="feed-news"></p>
  <ul id="feed-list">
    <li>…existing post…</li>
  </ul>
</div>

Code

Prepend new nodes instead of replacing the region, leave focus untouched, and report the count through the live region. The toggle lets the user stop updates entirely, satisfying Pause, Stop, Hide.

feed-update.js
let timer = null;

function addNew(items) {
  // Insert without re-rendering — focus & scroll stay put
  for (const item of items) list.prepend(renderItem(item));
  news.textContent = `${items.length} new posts`;  // polite announce
}
function poll() { fetchLatest().then(addNew); }

function play()  { if (!timer) timer = setInterval(poll, 30000); }
function pause() { clearInterval(timer); timer = null; }

toggle.addEventListener("click", () => {
  const paused = toggle.getAttribute("aria-pressed") === "true";
  paused ? play() : pause();
  toggle.setAttribute("aria-pressed", String(!paused));
});
play();

How to fix

  1. Insert new content without replacing the region; never reset focus or scroll position on a refresh.
  2. Avoid <meta http-equiv="refresh"> and full-page reloads on a timer — they discard everything the user was doing.
  3. Announce updates politely with aria-live="polite" (or role="status") so they don’t interrupt the current task.
  4. Give the user a control to pause or stop the auto-updates, as 2.2.2 requires.
  5. Where you can, prefer a user-triggered “Show new posts” button over silent background updates.

Recap

  • Before a session time limit expires, warn the user with at least 20 seconds to respond and give them a simple way to extend it without losing work (2.2.1).
  • Give any carousel or auto-advancing content that runs longer than 5 seconds a visible pause, stop, or hide control — and don’t restart it on its own (2.2.2).
  • When content refreshes automatically, insert it without moving focus or reading position; announce it politely in a live region and let the user choose when to view it (2.2.2).

All three are Level A. Meeting them satisfies WCAG 2.2, EN 301 549, Section 508, and ADA Title II at once — give people enough time and let them control what moves, and you meet them all.