Search topics
Session timeouts with no warning or way to extend
A checkout signs people out 15 minutes after the page loaded, with no warning or only a banner a screen reader never reads, and everything typed into the form is lost.
setTimeout(signOut, 15 * 60 * 1000) The problem
People who type, read or move more slowly are signed out in the middle of checkout and lose what they entered, with no warning they can perceive and no way to ask for more time.
A stationery shop's checkout asks for a delivery address and offers a gift message. The customer is signed in, and the shop signs people out 15 minutes after their last request to the server, a security default copied from a framework's example. Typing into a form sends nothing to the server, so the clock keeps running while someone writes. At 15 minutes the page jumps to the sign-in screen: "Your session has expired." Nothing on the page said there was a limit, nothing warned that it was close, and the address and message are gone. Many sites have only the server half of this: nothing happens on screen, and the form is lost when the customer presses Continue.
Some sites do warn. Two minutes before the end, a banner appears at the top of the screen with a Stay signed in button. It looks like a fix, and it works for a sighted mouse user who notices it. But the banner is added at the end of the page, nothing moves focus to it and nothing reads it out, so a screen reader user hears nothing, a magnifier user zoomed in on the form never sees it, and a keyboard user has to Tab past every field and footer link to reach the button.
Success Criterion 2.2.1 Timing Adjustable covers every time limit the content sets, and that includes a session the site's own server ends; only limits the content doesn't control, such as a browser's or a network's, are left out. For each one, people must be able to turn it off before they meet it, adjust it to at least ten times the default, or be warned before time runs out and given at least 20 seconds to extend it with a simple action, at least ten times. Three exceptions remain: a real-time event such as an auction, a limit that is essential to the activity, and a limit longer than 20 hours.
The first checkout meets none of these. The second does warn, but only by drawing the warning. 2.2.1 doesn't say how a warning must be given, but a warning has to reach the person it warns: one that a screen reader never reads, that is outside a magnifier's view, and that a keyboard user can only reach after everything else on the page leaves those people with the same deadline and no notice. W3C's technique for this warning, SCR16, uses the browser's own confirm dialog, which every user meets at once.
How long 15 minutes is depends on how someone fills in a form. A gift message typed by switch, or an address checked against a letter, can easily take longer, and the people who need longer are the ones who lose the most when the form empties: they have to do the slow part twice. On a phone, the on-screen keyboard shrinks the visible part of the page while someone types, and a banner pinned to the top of the screen can end up out of view.
4 of 10 groups affected
- No vision (affected)
- Low vision (affected)
- Colour vision (not affected)
- No hearing (not affected)
- Hard of hearing (not affected)
- No speech (not affected)
- Motor (affected)
- Reach, strength (not affected)
- Cognitive (affected)
- Seizures (not affected)
- Limited manipulation Typing with a switch, a head pointer, an on-screen keyboard or voice control takes much longer, so a delivery address and a gift message can take more than 15 minutes, and the page signs them out mid-sentence.
- Limited language, cognitive and learning abilities People who read slowly, check each detail twice, or step away to find a card or an address meet the limit while they are still working, and have to start again from an empty form.
- Without vision Screen reader users take longer to move through a form, and a warning banner that is only drawn at the top of the screen is never read to them, so the first they hear is the sign-in page.
- Limited vision Screen magnifier users zoomed in on the form don't see a banner at the top of the screen, and lose the page while it is out of view.
The limit lives in a script's timer or in the server's session settings, and neither shows in the page a scan reads. The warning, where there is one, appears only after 13 minutes, long after any scan has finished. axe reports the old form of this failure, a <meta http-equiv="refresh"> that reloads or redirects the page after a delay, which is W3C's failure F40 and F41. The audit also flags any page that shows a countdown or talks about a session ending, for a person to check, but it can't see a limit that the page never mentions. AI assistants asked for "auto logout after inactivity" often write exactly the first checkout's timer, and when asked for a warning, add a toast or banner in a corner that is never announced. Only someone who waits it out, with a keyboard, a screen reader or a magnifier, sees the failure.
Try it
Edit the gift message, then skip ahead and see what the shop does before its 15-minute limit.
The demo clock above the window stands in for the minutes; it is not part of the shop, and typing doesn’t reset it, just as typing sends nothing to the server. The last two minutes run in real time. In Broken 2, try reaching the warning with Tab, and read what a screen reader would say.
Broken Can you fix this?
Screen reader says:
(Skip ahead to 2 minutes before the limit)
The fix
Let the session outlast the visit, or warn people before it ends in a dialog they meet at once, and let one press keep them signed in.
- 1Make every time limit longer than 20 hours, let people turn it off, or warn them before it ends.
- 2Where a limit stays, warn at least 20 seconds before the end, and let one action extend it, as often as people need.
- 3Put the warning where focus is, so it is seen, read out and reached first.
A session that signs out with no warning
// A session ends 15 minutes after the last request.app.use(session({ secret: process.env.SESSION_SECRET, rolling: true, cookie: { maxAge: 15 * 60 * 1000 } }));A warning dialog that keeps people signed in
// The same 15-minute session, renewed by any request.app.use(session({ secret: process.env.SESSION_SECRET, rolling: true, cookie: { maxAge: 15 * 60 * 1000 } }));app.post('/session/extend', (req, res) => res.sendStatus(204));The page signs people out 15 minutes after it loaded, whatever they are doing, and nothing lets them turn the limit off, lengthen it or ask for more time. Everything typed into the form is lost with the page.
The limit stays, but nobody meets it unawares. Two minutes before the end, showModal() opens the warning over the page and moves focus to Stay signed in, so a screen reader can read the title, the time left and the button, a magnifier follows focus to it, and a keyboard user is already on it. One press, or Escape, renews the session on the server and starts the page's clock again, as many times as people need, and the browser puts focus back where it was, so people carry on typing. If nobody answers, the session ends as before, which is what the limit is for on a shared computer.
Both pass 2.2.1, and which one fits depends on what the session protects, not on accessibility. If the 15 minutes came from a default nobody chose, make the session longer than 20 hours: no one is interrupted, and nothing needs building. If the limit is there for security, keep it and add the warning dialog, timed for your own limit; it costs a dialog, a few lines of script and an endpoint, and it keeps the protection for people who really have walked away. Never keep the banner that is only drawn on screen.
Turning the limit off before people meet it also passes. A "Give me more time" box at the start of a long form, or a "Keep me signed in" box at sign-in that makes the session longer than 20 hours, lets people turn the limit off, or set one at least ten times as long, before they meet it, which 2.2.1 accepts. It only helps people who notice it and understand what it does, so on a security-bound site keep the warning as well.
How long to warn. 20 seconds is the minimum, chosen so that almost everyone can press a single key or switch. Reading a dialog with a screen reader, or noticing it after looking away, takes longer; two minutes costs nothing and covers far more people. Count the warning from the server's limit, and if the page itself makes requests (an address lookup, a saved draft), restart the page's clock on each one, so the two agree.
The time left. Give the time in the dialog's text, as the fix does. A clock that counts down every second can be drawn as well, but give it aria-hidden="true" and keep it out of any live region, or a screen reader reads every second over the button.
Count from what people do. The fix counts from the last request, as the server does. A site that wants active people never to see the dialog can also renew the session while they type, for example with one request a minute while input events arrive, and keep the dialog for when they stop.
Asleep and in other tabs. A laptop that sleeps through the limit wakes up past it with no warning ever shown. Timers don't run while it sleeps, so whenever one fires, and when the page is shown again (visibilitychange), compare Date.now() with when the limit started; once it has passed, ask the server whether the session is still there, and if it isn't, say so on the page and keep what was typed. When people have several tabs open, renew the session from whichever tab answers, and close the dialog in the others, for example through a BroadcastChannel.
Keep what was typed. Even with a warning, someone who misses it loses the form. Saving a draft, in the browser or on the server, and putting it back after people sign in again goes beyond 2.2.1: it is what 2.2.5 Re-authenticating asks for, a topic of its own.
Timed holds. A countdown on seats or stock held during checkout is a time limit too. W3C's own examples treat a two-minute hold on tickets that can sell out quickly as essential, and move as much of the process as possible, such as entering names and payment details, out of the timed part; where the hold can be extended, the same warning dialog with an "Extend time" button passes. Showing the countdown is not adjusting it: a clock in the corner with no button is still a limit nobody can change.
Not a fix. role="alert" on a banner that is in the page before its message is written into it gets the message read out, but focus stays in the form and the button stays at the end of the page. A longer fixed limit, such as 60 minutes instead of 15, is still a limit someone will meet without warning.
Fixing this with an AI coding assistant? Get this guide as Markdown, or get every guide in your coding agent.
Verify the fix
5 checks, no mouse.
Test with: Keyboard, Screen reader, Zoom
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the session settings, the sign-in flow or the warning change.
Limits
This covers time limits that end a session or a page, and one way of warning about them. Content that moves, scrolls or updates by itself has its own criterion and guide: tickers, carousels and animations that can't be paused. A message that disappears on a timer is a 2.2.1 limit too, with its own guide: toasts that vanish before you can read or undo them. A page that reloads or redirects itself on a timer, as a delayed meta refresh does, is a topic of its own. Telling people how long they can be idle before they lose data, and designing a process with no timing at all, are AAA topics of their own. Whether a limit is really essential or part of a real-time event is a judgement only the people who run the service can make, and screen readers handle dialogs that open by themselves differently: test the warning with the ones your users have.
This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.
References
- WCAG Understanding: timing-adjustable (opens in a new tab)
- WCAG Technique SCR16 (opens in a new tab)
- WCAG Technique SCR1 (opens in a new tab)
- WCAG Technique F40 (opens in a new tab)
- ARIA APG: alertdialog (opens in a new tab)
- developer.mozilla.org: dialog (opens in a new tab)
- cheatsheetseries.owasp.org: Session_Management_Cheat_Sheet (opens in a new tab)
Keep this fixed as your code changes.
Turn guides like this into rules your coding agents and CI follow, then have the result retested with a keyboard and screen readers.