Skip to main content

Notes

Why Promo Modals and Exit-Intent Popups Are an Accessibility Landmine

Quietramp ·

Why Promo Modals and Exit-Intent Popups Are an Accessibility Landmine

A promo modal — “Get 10% off your first order,” triggered when a shopper moves toward the browser’s close button, or after a few seconds on the page — is one of the most common e-commerce UI patterns, and one of the easiest to build inaccessibly. It’s a custom overlay, usually a <div> and some JavaScript rather than a native browser dialog, that has to take over keyboard focus, announce itself, and hand focus back cleanly when it closes. Skip a step and the popup meant to capture an email address instead traps a keyboard user on the page, or interrupts a screen-reader user mid-sentence with no way to tell what just happened.

Quick answer: an accessible promo modal needs keyboard focus moved into it the moment it opens, a role="dialog" (or role="alertdialog") container with aria-modal="true" and an accessible name, a keyboard-operable close mechanism (a real close button plus Escape) that returns focus to wherever the user was, background content marked inert so assistive technology can’t reach it while the modal is open, and — if it appears or disappears on a timer — a way to turn that timer off. None of this requires abandoning promo modals as a pattern; it requires building the one most e-commerce sites already have correctly.

This article covers technical accessibility practice, not legal advice — it explains what WCAG 2.1 AA requires for dialog and modal UI and how to meet it, not whether a specific implementation carries legal risk.

Why this pattern breaks so often

Promo modals are almost never native HTML <dialog> elements — most are a positioned <div> shown and hidden with JavaScript, styled to look like a dialog but with none of a real dialog’s built-in behavior. A native <dialog> opened with showModal() handles focus containment and background inertness automatically; a hand-rolled <div> overlay does none of that unless someone writes the code for it, even though it can look identical on screen. Compounding that, these modals are frequently bolted on late by a marketing tool or a lightweight script, without the review the rest of the checkout flow got. A modal that traps focus with no way out, or interrupts a screen reader with an unannounced overlay, is a bug a purely visual QA pass won’t catch — the page looks fine on screen the entire time.

Trap keyboard focus inside the modal — correctly

When the modal opens, keyboard focus needs to move into it immediately, and Tab/Shift+Tab need to cycle only through its own controls (email field, submit button, close button) rather than reaching the page content behind it. This is intentional focus containment, and it’s correct behavior — the risk is doing it without an exit.

WCAG 2.1.2 No Keyboard Trap (A) is the criterion this pattern fails most often: focus gets trapped inside the modal, but there’s no keyboard-operable way to close it — the close control is a mouse-only “×” with no button semantics, or it isn’t in the tab order at all. A user who can’t reach the mouse is stuck on the page until they reload it. The fix isn’t to remove the focus trap; it’s to make sure the trap has a working door. The WAI-ARIA APG Dialog (Modal) Pattern is explicit that Tab and Shift+Tab shouldn’t move focus outside an open modal — and equally explicit that the tab sequence has to include a visible, keyboard-operable close control.

WCAG 2.4.3 Focus Order (A) covers the other half: focus needs to land inside the modal the instant it opens (usually the heading or first field, not left on whatever was last focused), and move back to a sensible place when it closes. For an exit-intent popup nothing triggered it by a click, so “return focus to the trigger” doesn’t quite apply — returning focus to whatever was focused immediately beforehand, or to the page’s main content region, is the practical equivalent.

Give the dialog a role, name, and modal state assistive tech can detect

A <div> styled to look like a dialog is, to a screen reader, just a <div> — no announcement, no indication it’s a dialog, no signal that the rest of the page is temporarily unavailable. WCAG 4.1.2 Name, Role, Value (A) requires the modal’s role, accessible name, and state to be programmatically exposed, not just visually implied.

The fix: put role="dialog" (or role="alertdialog" only if the content genuinely needs immediate attention, which a discount offer usually doesn’t) on the container, add aria-modal="true", and give it an accessible name via aria-labelledby pointing at the visible heading. Per MDN’s documentation on aria-modal, the attribute tells assistive technology that content outside the dialog is inert — but MDN is explicit it doesn’t make that true by itself; the underlying behavior still has to be implemented in code. The most reliable way to do that for a hand-built overlay is the HTML inert global attribute: applying inert to the rest of the page while the modal is open removes it from the tab order and accessibility tree entirely, so a screen reader can’t wander into content that’s supposed to be unreachable.

Make it dismissible without punishing anyone

A close button is necessary but not sufficient. It needs a real accessible name — aria-label="Close" on an icon-only “×”, not a bare unlabeled <span> — and it needs to be reachable early in the tab order, not buried after a long form. Escape should close the modal too; it’s the de facto standard keyboard shortcut for dismissing an overlay, and its absence is a fast way to frustrate a keyboard user who doesn’t know where else to look for an exit. Don’t make the only way out be submitting the email field, or shrink the close control to push users toward converting instead of closing — that’s a dark pattern regardless of accessibility, and it compounds one problem into two.

Handle auto-appear and auto-dismiss timing deliberately

Exit-intent popups are timed by nature — appearing after a delay or a mouse-leave event, and sometimes auto-dismissing after a few seconds of no interaction. WCAG 2.2.1 Timing Adjustable (A) requires that a time limit set by the content — including a popup that disappears on its own — be turned off, extended, or adjusted by the user, unless a specific exception applies. A modal that vanishes before a screen-reader user has even located it, with no way to keep it open, is exactly the pattern this criterion exists for. WCAG 2.2.2 Pause, Stop, Hide (A) applies too if the modal auto-rotates or auto-advances through multiple offers — that motion needs a way to pause it.

The simplest compliant choice, and the one that tends to convert better anyway: don’t auto-dismiss on a timer at all — let the user close it. A delayed appearance (waiting a few seconds, or triggering on exit intent) is unaffected by 2.2.1, which governs time limits a user is forced to react to, not when content first shows up.

A minimal accessible pattern

<div id="promo-modal" role="dialog" aria-modal="true" aria-labelledby="promo-title" hidden>
  <h2 id="promo-title">Get 10% off your first order</h2>
  <button type="button" aria-label="Close" data-close-modal>×</button>
  <form>
    <label for="promo-email">Email address</label>
    <input type="email" id="promo-email" name="email" autocomplete="email" required>
    <button type="submit">Send my code</button>
  </form>
</div>
function openModal(modal, background) {
  modal.hidden = false;
  background.inert = true;
  modal.querySelector('h2').setAttribute('tabindex', '-1');
  modal.querySelector('h2').focus();
  document.addEventListener('keydown', onKeydown);
}
function closeModal(modal, background, restoreFocusTo) {
  modal.hidden = true;
  background.inert = false;
  document.removeEventListener('keydown', onKeydown);
  restoreFocusTo?.focus();
}
function onKeydown(e) {
  if (e.key === 'Escape') closeModal(/* ... */);
}

The pattern above is illustrative, not a drop-in component — a real implementation needs full focus-cycling on Tab within the modal, omitted here for brevity. The WAI-ARIA APG’s own dialog example is a fuller reference for building one from scratch.

FAQ

Does WCAG require promo modals to not exist at all? No. It requires that any interactive UI, including one built for marketing rather than core site function, be operable by keyboard and correctly exposed to assistive technology.

Is aria-modal="true" enough by itself? No — per MDN, it tells assistive technology the rest of the page is inert but doesn’t make that true. The actual focus containment and background inertness still have to be implemented, typically with inert or equivalent JavaScript.

What if the modal is built by a third-party marketing tool, not our own code? The same WCAG criteria still apply regardless of which vendor’s script produced it. Many popup tools expose configuration options (custom HTML, close-button labeling) that fix these issues without a rebuild — check the vendor’s settings first.

Get a full audit of your site’s interactive UI

Promo modals are one interaction pattern among many that a purely visual review won’t catch — a full Quietramp audit checks your whole site against WCAG 2.1 AA, verified by a person operating it with a keyboard and screen reader, not just an automated scan. See a real sample report of what that looks like, or check pricing — $890 one-time, $99/month if you want ongoing re-checks after fixes ship.


This is educational content about technical accessibility standards, not legal advice. Meeting WCAG 2.1 AA does not guarantee legal compliance with the ADA or any other law, and nothing here should be read as a compliance guarantee. Consult qualified counsel for legal risk questions.

This article was drafted with AI assistance and reviewed by a person for accuracy before publication.

← Back to Notes