# Timed pop-ups that open while you read or type

Source: https://easeweb.dev/learn/timed-pop-ups
Topics: Dialogs and overlays > Pop-ups and updates can be put off until later (WCAG 2.2.4)
The fix: Let people decide when the offer comes. Either drop the timer and open the same pop-up from a "Get 10% off your first order" button in the page, or keep the timer behind a setting at the top of the page that turns the pop-up off, and remember the choice.
Test with: keyboard, screen reader
References: https://www.w3.org/WAI/WCAG22/Understanding/interruptions.html https://www.w3.org/WAI/WCAG22/Techniques/general/G75 https://www.w3.org/WAI/WCAG22/Techniques/general/G76 https://www.w3.org/WAI/WCAG22/Techniques/client-side-script/SCR14 https://www.w3.org/WAI/WCAG22/Techniques/failures/F41 https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal

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

Fernleaf Paper sells greeting cards and posts them for you. The shop wants new visitors' email addresses, and its pop-up is drawn with care: a soft card over the dimmed page, "Get 10% off your first order", one email field, a Sign up button and a × in the corner. It opens ten seconds after a page loads, a delay set in the shop's marketing tool so that it never greets people the moment they arrive.

On the page for a wildflower birthday card, someone is writing the message to go inside it. They have typed "Happy birthday! Hope you" when the ten seconds are up. The pop-up opens over the page and focus moves into its email field, so "have a lovely" goes into the email address instead of the card. A screen reader user who was reading the card's description hears the pop-up's heading and field in its place. Someone who finds it hard to hold their attention is now looking at an offer, not at the message they were halfway through. The × and Escape close the pop-up, and nothing on the page lets anyone put it off or turn it off before it comes.

## Why it fails

Success Criterion 2.2.4 Interruptions (Level AAA) asks that interruptions can be postponed or suppressed by the user, except interruptions involving an emergency. Its Understanding page puts the intent as letting people turn off updates from the author or the server, and its one example is a preferences page with an option to postpone all updates and alerts until the end of the session. W3C's technique G75 does it through a preference, or by warning people that an update is about to come and letting them suppress it; G76 lets people ask for the update instead.

The pop-up offers neither. It has a × and closes on Escape, but both come after the interruption: by the time either can be pressed, the sentence has been broken off. Nothing before it lets anyone choose not to have it, or to have it later.

The browser has no reason to hold it back. A timer runs whether or not anyone is in the middle of something, and `showModal()` does what a modal dialog should: it puts the dialog over the page, makes the page behind it inert and moves focus into it, here to the field marked `autofocus`. Every part of the pop-up is built right. What fails is when it opens.

## Who is affected

The timer was chosen so that the pop-up doesn't open the moment people arrive. Ten seconds is long enough to have started something, so it lands on people while they are doing what they came for, and the more slowly they read or type, the more likely they are to be in the middle of it.

## What automation and AI miss

Nothing in the markup is wrong, and a scan that loads the page and leaves within a few seconds is gone before the pop-up opens. The audit watches the timers a page starts before anyone has used it, and sends one whose callback calls `showModal()`, or a function named like `openPopup()`, to review as `ew:timed-popup-manual-check`: only a person can tell whether a setting already turns it off, or whether it is an emergency. Pop-ups from a marketing tool are often set up in its dashboard and loaded through code the audit can't recognise, and a chat greeting is set on the chat vendor's side, so the audit misses those; the dashboards have to be checked by hand. Asked for "a newsletter pop-up after ten seconds", AI coding tools write a `setTimeout` like the one in the Before.

## Before: a sign-up pop-up that opens after ten seconds

```html
<main>
  <h1>Wildflower birthday card</h1>
  <form class="card-form">
    <label for="card-message">Message inside the card</label>
    <textarea id="card-message" name="message" rows="4"></textarea>
    <button>Add to basket</button>
  </form>
</main>
<dialog id="signup" aria-labelledby="signup-title">
  <button type="button" class="signup-close" aria-label="Close">×</button>
  <h2 id="signup-title">Get 10% off your first order</h2>
  <p>Sign up for news of new designs and sales.</p>
  <form method="dialog">
    <label for="signup-email">Email address</label>
    <input id="signup-email" name="email" type="email" autocomplete="email" autofocus required>
    <button>Sign up</button>
  </form>
</dialog>
```

```js
const signup = document.getElementById('signup');
signup.querySelector('.signup-close').addEventListener('click', () => signup.close());

// The sign-up opens ten seconds after the page loads.
setTimeout(() => signup.showModal(), 10000);
```

The pop-up itself is right: it is a native dialog, its × is a named button, Escape closes it, and closing it puts focus back where it was. Where it fails is when it opens:

- It opens ten seconds after the page loads, whatever the person is doing at that moment.
- Focus moves into the email field, so typing meant for the card goes into the pop-up, and a screen reader stops reading the page to read the pop-up.
- Nothing on the page lets anyone put it off or turn it off before it comes.

## After: a button that opens the offer

```html
<p class="offer-bar">
  New here?
  <button type="button" id="offer-open">Get 10% off your first order</button>
</p>
<main>
  <h1>Wildflower birthday card</h1>
  <form class="card-form">
    <label for="card-message">Message inside the card</label>
    <textarea id="card-message" name="message" rows="4"></textarea>
    <button>Add to basket</button>
  </form>
</main>
<dialog id="signup" aria-labelledby="signup-title">
  <button type="button" class="signup-close" aria-label="Close">×</button>
  <h2 id="signup-title">Get 10% off your first order</h2>
  <p>Sign up for news of new designs and sales.</p>
  <form method="dialog">
    <label for="signup-email">Email address</label>
    <input id="signup-email" name="email" type="email" autocomplete="email" autofocus required>
    <button>Sign up</button>
  </form>
</dialog>
```

```js
const signup = document.getElementById('signup');
signup.querySelector('.signup-close').addEventListener('click', () => signup.close());

// The sign-up opens when someone asks for it.
document.getElementById('offer-open').addEventListener('click', () => signup.showModal());
```

1. Delete the timer, so the pop-up never opens by itself.
2. Put a button at the top of the page that says what it opens, "Get 10% off your first order". The offer is in front of every visitor from the moment the page loads, and in the same place on every page.
3. Open the same pop-up from that button. Focus moves into the email field only because someone pressed the button, and closing the pop-up brings them back to it.

This fixes the Before and passes 2.2.4: nothing opens until it is asked for, so there is nothing to put off. The pop-up keeps its look; what changes is a slim bar at the top of the page that is there from the start.

## After: a setting that turns the pop-up off

```html
<p class="offer-setting">
  <input type="checkbox" id="offer-popups" checked>
  <label for="offer-popups">Show offers in pop-ups</label>
</p>
<main>
  <h1>Wildflower birthday card</h1>
  <form class="card-form">
    <label for="card-message">Message inside the card</label>
    <textarea id="card-message" name="message" rows="4"></textarea>
    <button>Add to basket</button>
  </form>
</main>
<dialog id="signup" aria-labelledby="signup-title">
  <button type="button" class="signup-close" aria-label="Close">×</button>
  <h2 id="signup-title">Get 10% off your first order</h2>
  <p>Sign up for news of new designs and sales.</p>
  <form method="dialog">
    <label for="signup-email">Email address</label>
    <input id="signup-email" name="email" type="email" autocomplete="email" autofocus required>
    <button>Sign up</button>
  </form>
</dialog>
```

```js
const signup = document.getElementById('signup');
signup.querySelector('.signup-close').addEventListener('click', () => signup.close());

const offerPopups = document.getElementById('offer-popups');
offerPopups.checked = localStorage.getItem('offer-popups') !== 'off';
offerPopups.addEventListener('change', () => {
  localStorage.setItem('offer-popups', offerPopups.checked ? 'on' : 'off');
});

// The sign-up opens ten seconds after the page loads, unless pop-ups are turned off.
setTimeout(() => {
  if (offerPopups.checked) signup.showModal();
}, 10000);
```

1. Put a checkbox, "Show offers in pop-ups", at the top of the page, before the main content, so people reach it before the pop-up does.
2. When the timer fires, open the pop-up only if the checkbox is still ticked.
3. Save the choice and read it back when the page loads, so someone who turned pop-ups off doesn't meet them on the next page or the next visit.

This fixes the Before, and meets the wording of 2.2.4, which allows interruptions people can suppress. It doesn't meet the test of W3C's technique G75, which asks for the pop-up to be off until people turn it on, or for a warning just before it opens with a way to stop it. It leaves the pop-up on until someone turns it off: a visitor who starts on the card, or arrives from a link straight to the message field, still has it open over their work before they find the checkbox.

## Which option to use

The button passes 2.2.4 as technique G76 describes it; the setting meets the criterion's wording but not G75's test. The button is recommended: nothing interrupts anyone, so nobody has to find a setting first, while the setting is not enough on its own, because the pop-up still opens over the work of everyone who hasn't found it yet. The button costs the shop the visitors who would only have signed up when the offer was put in front of them; a bar at the top of every page keeps the offer in view without stopping anyone. Keep the setting only when the timed pop-up has to stay, put it first on the page, and remember the choice.

## Implementation decisions

**Other triggers that nobody presses.** Marketing tools also open pop-ups when the visitor has scrolled part of the way down, has been on the site for a while, or moves the pointer towards the address bar, as if to leave. Each of these opens without being asked for, in the middle of whatever the person is doing, and takes the same fix: open it from a button, or put it behind the setting.

**Remembering that the pop-up was closed.** Marketing tools can be set to stop showing a pop-up for some days once it has been closed. That spares people a second interruption, which is worth doing, but the first one still comes, and nothing before it lets anyone say no, so it doesn't fix the Before on its own.

**A chat that greets people by itself.** Chat tools can send a greeting a set time after someone arrives, and pop the chat widget up with it: Zendesk's default Proactive Greeting trigger, inactive until a site turns it on, sends one to new visitors 30 seconds after they arrive. It is the same failure. Leave the greeting for when the panel is opened from its launcher, or put it behind the same setting. How the launcher and its panel are reached is the subject of [chat buttons that Tab skips, and close buttons with no name](/learn/chat-launchers), and whether its messages are read out, of [chat widgets that never announce their messages](/learn/chat-widgets).

**Update prompts.** A web app that has a new version can tell people quietly, in a line or a button that waits ("A new version is ready. Reload"), instead of a dialog over their work. Reload when they press it.

**Emergencies may interrupt.** WCAG's emergency is a sudden, unexpected situation that needs action at once to protect health, safety or property, and the Understanding page counts messages that warn of data loss or a lost connection among them. A warning that a session is about to end and take the order with it may interrupt, and should, as [session timeouts with no warning or way to extend](/learn/time-limits) describes. A sale that ends tonight is not an emergency. How such a message is announced, and why routine news should wait for the screen reader, is in [alerts that cut off VoiceOver for routine updates](/learn/urgent-alerts).

**Pages that reload themselves.** A page with `<meta http-equiv="refresh">` interrupts by reloading, and W3C's failure F41 counts it against 2.2.4 as well as 2.2.1 and 3.2.5. Delete it, as [pages that reload or redirect themselves on a timer](/learn/meta-refresh) shows; a list that rewrites itself on a timer is [auto-updating feeds that change while people read them](/learn/auto-updating-feeds).

**The pop-up itself.** This guide is about when a pop-up opens. Once it is open, it still needs a keyboard way out, as in [pop-ups that can't be closed with a keyboard](/learn/closing-pop-ups), and focus kept inside and brought back, as in [modals that leave keyboard focus behind them](/learn/modal-focus-management).

## Verify the fix

1. Search the scripts for `setTimeout` and `setInterval` callbacks, and scroll, `mouseout` and `visibilitychange` handlers, that open a dialog or show an overlay. Each one first checks a setting that people can turn off, or there are none.
2. Put the mouse aside, load the page and leave it alone for a minute: no pop-up opens, or, with the setting, it opens only while the setting is on.
3. Load the page again and type a message into a field for a minute: every character goes into that field. With the setting, untick it before the time is up, reload the page, and check that it is still unticked and nothing opens.
4. With a screen reader, read the page from the top for a minute: reading is never moved into a pop-up, and the button or the setting says plainly what it does.
5. In the marketing and chat tools' dashboards, check every pop-up, slide-in and greeting: each opens on a click, or sits behind the setting.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after any change to the pop-up, the tools that show it, or their campaigns.

## Limits

This is a starting pattern, not a guarantee for every application. It covers one sign-up pop-up opened on a timer. The snippets leave out what `Sign up` does with the address. Marketing and chat tools change their settings and campaigns without a deploy on your side, so their pop-ups need checking again after each change. Whether an interruption is an emergency is a judgement about your service, not something code can decide. Test the complete journey with the assistive technology your visitors use.

---

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