# Countdown timers that screen readers keep reading out

Source: https://easeweb.dev/learn/countdown-timers
Topics: Live updates > Live scores, tickers and timers do not announce every change (WCAG 4.1.3, 2.2.2)
The fix: Either show when the sale ends, as a fixed time in a time element, so nothing changes and nothing is announced; or keep the countdown, take it out of the live region and give it role="timer", which screen readers don't announce on each change, with a Stop countdown button before it that swaps the ticking time for the end time.
Test with: screen reader, keyboard
References: https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide.html https://www.w3.org/WAI/WCAG22/Techniques/general/G186 https://www.w3.org/TR/wai-aria-1.2/#timer https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/timer_role https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-live https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/time https://webaim.org/projects/screenreadersurvey10/

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.

## What happens

A linen shop is running an autumn sale, and every product page opens with a banner: "Sale ends in 02:14:09", counting down by the second. The developer wanted screen reader users to know the sale is running out, so the banner is marked `aria-live="assertive"`.

A screen reader user opens a tea towel's page and reads the description, line by line. With JAWS, NVDA or Narrator, each line is read in full, and then the clock: "Hand-woven linen tea towel, washed for a soft feel from the first use. 02:14:06." The next line, then "02:14:03". If they stop to think, the times keep coming, one a second. With VoiceOver on a Mac or an iPad, the new time cuts the line off part way through, "Hand-woven linen, 02:14:08", and the rest of the line is never read. Either way, the description, the price and the Add to cart button are read in between clock times. A sighted visitor who finds the ticking numbers distracting has no way to stop them: the banner has no button, and it ticks for as long as the page is open.

## Why it fails

The banner fails twice. The first failure is under 4.1.3. An `aria-live="assertive"` region asks the screen reader to present each change immediately, and ARIA lets it drop whatever was waiting. Understanding 4.1.3 says status messages should reach people "in a way that doesn't unnecessarily interrupt their work", and lists `role="alert"` or `aria-live="assertive"` on content that is not important and time-sensitive among its failures. The second that has just passed is neither. Screen readers don't all do what the region asks. In the 2026 releases, JAWS, NVDA and Narrator read an assertive region as they read a polite one, once the sentence or line in progress has ended, and VoiceOver on macOS and iPadOS interrupted. So every change is read, and where the screen reader honours the region, read over the page. The banner was never meant to be a status message: the script changes one number, and the region turns every change into an announcement.

The second failure is under 2.2.2. A countdown is auto-updating information: in the words of Understanding 2.2.2, content that updates based on a preset time interval. It starts by itself and sits beside the product, so WCAG asks for a way to pause, stop or hide it, or to control how often it updates, and there is no five-second allowance for auto-updating content. Understanding 2.2.2 also notes that such content "can also cause problems for screen readers", which is exactly what the live region turns it into here.

## Who is affected

The banner is on every product page during the sale, so the times follow the visitor through the whole shop, and they stop only when the sale ends. On phones and tablets, most screen reader users use VoiceOver (70.6% of mobile respondents in WebAIM's 2024 survey), the screen reader that cut in on macOS and iPadOS in those tests.

## What automation and AI miss

A scan sees a live region that is on the page from the start, with text in it, and passes it. Nothing in the markup says that the text changes every second. The audit watches the page for a few seconds before it touches anything. It reports text in an assertive region that changes by itself three times or more, and flags any text that keeps changing by itself, whatever region it is in, for a person to check, but it can't tell whether a Stop button elsewhere on the page controls it. AI assistants asked to make a countdown accessible may add `aria-live` to it, which is what produced this banner, or `role="timer"` together with `aria-live="assertive"`, which brings the interruption back. Only listening to the page while the countdown runs shows the problem, and what it sounds like depends on the screen reader.

## Before: a countdown in an assertive live region

```html
<p class="sale-banner" aria-live="assertive">
  Sale ends in <span id="sale-clock">02:14:09</span>
</p>
```

```js
const end = new Date('2026-10-31T23:59:00Z');
const clock = document.querySelector('#sale-clock');

function tick() {
  clock.textContent = timeLeft(end); // "02:14:08"
}

setInterval(tick, 1000);
```

The script changes the number every second, inside a region that is assertive, so the changes are announced: after each line in JAWS, NVDA and Narrator, and over the line in VoiceOver. There is nothing on the page that stops it.

## After: the end time, written once

```html
<p class="sale-banner">
  Sale ends <time id="sale-end" datetime="2026-10-31T23:59:00Z">31 October at 23:59</time>
</p>
```

```js
const end = new Date('2026-10-31T23:59:00Z');
const endText = document.querySelector('#sale-end');

// Written once, in the visitor's own time zone; nothing changes after that.
endText.textContent = end.toLocaleString([], { day: 'numeric', month: 'long', hour: '2-digit', minute: '2-digit' });
```

Nothing changes after the page loads, so there is nothing to announce and nothing to stop, and 2.2.2 has nothing to ask for. The banner has no live region: a screen reader reads "Sale ends 31 October at 23:59" once, when the person reaches it, as it reads any other line. The `time` element carries the exact moment in `datetime`, and the script rewrites the visible words in the visitor's own time zone, so they don't have to work out the difference. The banner loses its ticking, which some shops use to make a sale feel urgent.

## After: a silent countdown with a Stop button

```html
<p class="sale-banner">
  <button type="button" id="sale-stop">Stop countdown</button>
  <span id="sale-left">Sale ends in <span id="sale-clock" role="timer">02:14:09</span></span>
  <span id="sale-end" hidden>Sale ends <time datetime="2026-10-31T23:59:00Z">31 October at 23:59</time></span>
</p>
```

```js
const end = new Date('2026-10-31T23:59:00Z');
const clock = document.querySelector('#sale-clock');
const left = document.querySelector('#sale-left');
const endText = document.querySelector('#sale-end');
const button = document.querySelector('#sale-stop');

function tick() {
  clock.textContent = timeLeft(end); // "02:14:08"
}

let timer = setInterval(tick, 1000);

// Stopped, the banner shows the end time instead of a time that would soon be wrong.
button.addEventListener('click', () => {
  const stopping = !left.hidden;
  clearInterval(timer);
  if (!stopping) {
    tick();
    timer = setInterval(tick, 1000);
  }
  left.hidden = stopping;
  endText.hidden = !stopping;
  button.textContent = stopping ? 'Show countdown' : 'Stop countdown';
});
```

The countdown still ticks, but the banner is no longer a live region. The clock has `role="timer"`, a live region whose `aria-live` is `off` by default, so the screen reader says nothing as it changes, and reads the time left when the person reaches it. The Stop countdown button comes before the clock, so the keyboard reaches it first. Pressing it stops the updates and shows the end time in their place: a paused countdown shows a time that goes out of date, and Understanding 2.2.2 notes that an auction timer shown after a pause would present misleading information, while the end time stays true. Pressing it again brings the countdown back. The countdown keeps the urgency the shop wanted, and it ticks for everyone until they stop it.

## Which option to use

Both pass 4.1.3 and 2.2.2. The end time costs nothing to build, reads once for everyone, and gives nobody anything to stop, but it gives up the ticking. The silent countdown keeps the ticking, at the cost of a button, a little more script and a banner that moves for everyone until they find the button. Choose the end time when the shop can do without the ticking, and the silent countdown with its button when the ticking is part of how the sale is sold.

## Implementation decisions

**Polite is not a fix.** Changing the region to `aria-live="polite"` stops VoiceOver cutting into a line, but every screen reader still reads a new time after every line, so the page is read in between times. That is what JAWS, NVDA and Narrator already do with the assertive one. Understanding 4.1.3 warns that live regions and alerts risk making an application too "chatty" for a screen reader user. For a countdown the answer is the same as for the assertive one: no live region on the clock.

**`role="timer"` keeps it quiet.** A timer is a live region whose `aria-live` is `off` by default, so its changes are not announced. Don't add `aria-live` to it: `role="timer" aria-live="assertive"` is the Before again. Removing `aria-live` and leaving the role out would also stop the announcements; the role tells assistive technology what the changing number is, and some screen readers announce its name before its contents.

**Say a milestone, once, if it matters.** People rarely need to hear that a sale is nearly over. When they do, as with a booking held for a few minutes, write one message into a `role="status"` region at a milestone ("10 minutes left"), and keep the clock silent. MDN's timer example switches the timer's own role to `alert` at ten seconds; a separate status region announces the milestone once, without making the clock a live region at all. A time limit on the person's own task is a different failure, covered in [session timeouts with no warning or way to extend](/learn/time-limits).

**Keep the choice.** Store the stopped state, for example in `sessionStorage`, so the banner doesn't start ticking again on every product page the visitor opens.

**Hide is allowed too.** 2.2.2 accepts pause, stop or hide. A Close button that removes the banner passes, but it takes the sale's end with it, so pair it with the end time somewhere else on the page.

**Scores, feeds and prices.** The same failure shows up wherever something changes by itself, and the fixes follow the same lines:

- A live score: the match clock in `role="timer"` or no live region at all, and the score written into a `role="status"` region only when it changes ("Goal: Rovers 2, City 1"), with a button that pauses the updates.
- A live blog or news feed: new entries added to a `role="log"` region, which is polite, and a Pause updates button, since a feed that grows on its own is auto-updating content too.
- Prices and stock levels that refresh: no live region on the numbers, and a button or setting that stops the refresh or makes it less frequent.

**Moving and urgent are other topics.** A ticker that scrolls sideways is moving content, with its own fixes in [tickers, carousels and animations that can't be paused](/learn/moving-content). When a message really is urgent, `role="alert"` is right, and choosing between the two is covered in [alerts that cut off VoiceOver for routine updates](/learn/urgent-alerts).

## Verify the fix

1. Inspect the banner in the DOM: neither the clock nor anything around it has `aria-live`, `role="alert"` or `role="status"`, and a running clock has `role="timer"`.
2. With a screen reader, read the product page line by line for 30 seconds while the countdown runs. No time is read between the lines, and no line is cut off. Check with VoiceOver as well as with JAWS, NVDA or Narrator, since only VoiceOver cuts the lines off.
3. Move to the banner with the screen reader. It reads the time left, or the end time, once, with "Sale ends" before it.
4. Using only the keyboard, press Stop countdown, read on, and come back: the banner still shows the end time and has not started ticking again. On the end-time version, check that nothing on the banner changes after load.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the banner, its script or the sale component changes.

## Limits

Live regions behave differently across screen readers and browsers, and from one version to the next: what this guide describes is how the releases current in 2026 behaved. Understanding 4.1.3 also notes that, depending on the user's preferences, assistive technology may delay, suppress or transform status messages. This guide covers a countdown beside other content, on one page. Whether the updating is essential to an activity, as an auction's clock may be, is a judgement for the team, and 2.2.2 does not ask for a way to stop it then; even so, the clock does not need to announce every second. Test with the screen readers your users use.

---

Get this guide in your coding agent: MCP https://easeweb.dev/mcp (setup at https://easeweb.dev/services/developers)
