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.1AEN 301 549Section 508ADA 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
Warn the user before the limit is reached, not after — show a dialog with the
time remaining.
Give at least 20 seconds to respond, and let the user extend the session at
least ten times.
Make extending a single, obvious action (a Stay signed in button)
that resets the timer and preserves entered data.
Move focus to the warning dialog and use role="alertdialog" so
screen reader users are told immediately.
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.
Auto-advancing carousel with no pause control
WCAG 2.2 · 2.2.2AEN 301 549Section 508ADA Title II
A hero carousel that rotates on its own moves content the
user is trying to read. Pause, Stop, Hide says that any moving or auto-updating
content which starts automatically, lasts more than 5 seconds, and is shown alongside
other content must give the user a way to pause, stop, or hide it. A slow
reader can’t finish a slide before it changes; a switch or keyboard user can’t reach a
link before it scrolls away; and motion can be distracting or nauseating. Tying pause
only to mouse hover doesn’t count — keyboard and touch users never trigger it, and the
control must be operable by everyone.
Bad
The carousel auto-rotates every four seconds with no visible control. The slides
move regardless of the user’s reading speed, and there is nothing to stop them.
bad-carousel.html
<div class="carousel">
<div class="slide">…</div>
<div class="slide">…</div>
</div>
<script>
// Rotates forever; no pause, no stop, no hide
setInterval(nextSlide, 4000);
</script>
Good
A real Pause button sits inside the carousel, operable by keyboard,
pointer, and touch. The button’s label reflects its state, and pausing stops the
rotation until the user chooses to resume.
Wire the button to start and stop the interval and to update its pressed state and
label. Also pause on focus within the carousel and honour reduced-motion so the
rotation never starts for users who ask for less movement.
carousel-toggle.js
let timer = null;
const reduce = matchMedia("(prefers-reduced-motion: reduce)");
function play() { if (!timer) timer = setInterval(nextSlide, 5000); }
function pause() { clearInterval(timer); timer = null; }
toggle.addEventListener("click", () => {
const paused = toggle.getAttribute("aria-pressed") === "true";
if (paused) { play(); toggle.setAttribute("aria-pressed", "false"); }
else { pause(); toggle.setAttribute("aria-pressed", "true"); }
});
// Don’t auto-rotate if the user prefers reduced motion
if (!reduce.matches) play();
How to fix
Add a visible pause, stop, or hide control that is keyboard, pointer, and
touch operable — not hover-only.
Reflect the control’s state in its accessible name (toggle
aria-pressed and the label between Pause and Play).
Pause rotation when a slide receives focus or is hovered, so users reading a
slide aren’t interrupted.
Respect prefers-reduced-motion and don’t auto-rotate at all when
it is set.
The simplest fix is often to not auto-advance — let the user move slides
themselves, which sidesteps 2.2.2 entirely.
Content that auto-refreshes and moves focus
WCAG 2.2 · 2.2.2AEN 301 549Section 508ADA 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.
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
Insert new content without replacing the region; never reset focus or scroll
position on a refresh.
Avoid <meta http-equiv="refresh"> and full-page reloads on a
timer — they discard everything the user was doing.
Announce updates politely with aria-live="polite" (or
role="status") so they don’t interrupt the current task.
Give the user a control to pause or stop the auto-updates, as 2.2.2 requires.
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.