Modal Dialog Accessibility Test Script: Keyboard QA for Popups

Run a repeatable modal dialog accessibility test script before launch: trigger labels, initial focus, Tab and Shift+Tab containment, Escape and focus return.

James HollowayWeb Development Lead9 min read

If your team already ships popups, the risk is rarely that nobody thought about accessibility. It is that nobody re-checks it after the third content edit, the new form field, or the timing tweak that moved focus somewhere unexpected. A modal dialog accessibility test script fixes that by turning keyboard behaviour into a short, repeatable pass/fail routine instead of an annual review.

This script is deliberately narrow. It checks observable keyboard and focus behaviour on a staging page, in a couple of browsers, with JavaScript enabled. It does not certify WCAG conformance, does not replace screen reader testing with real users, and makes no legal or compliance claim about your site. If you need a wider audit, start from an accessibility checklist and treat this as the popup section of it. For the underlying mechanics and fixes, the companion piece on modal focus management covers entry, containment and return in more depth; this article is the test script you hand to whoever is doing the pre-launch check.

What the script checks, and the assumptions it makes

The script answers one question: when a visitor opens the popup with a keyboard, can they understand it, move through it, close it, and get back to where they were? Each test below produces a pass or fail, not a score.

Before you start, you need three things: a staging page that has the popup installed, a keyboard you will use without a mouse, and the popup configured in the WidgetJar panel so you know which part of the experience you control. WidgetJar is an embeddable widget platform: you create and configure widgets in the panel, then install the embed script on the registered website domain. That split matters later, because some failures are fixed in the popup configuration and some can only be fixed on the host page.

Keep one browser with devtools open for the whole run so you can inspect focus and the accessibility tree. If you are unsure whether the popup renders as a real dialog, the MDN dialog element reference explains that a dialog element represents a modal or non-modal dialog box, that modal dialogs block interaction with other UI, and that a dialog opened with showModal() is implicitly modal; the browser also handles dismissing modal dialogs with the Esc key for native implementations.

The seven-test keyboard script

Work through the tests in order on a fresh page load each time. Note the element that had focus before opening as your reference point.

Test 1: the trigger has an accessible name and is reachable

Tab from the top of the page until you reach the control that opens the popup. Pass if you can reach it without a mouse, the focus indicator is visible, and whatever is announced describes the action (a button reading "Open chat" or similar) rather than a bare icon or "click here". Fail if the trigger is a div you can only click, has no text alternative, or is skipped entirely by Tab.

Test 2: initial focus lands inside the dialog

Activate the trigger with Enter or Space. Pass if focus moves into the dialog — the first field, or the dialog heading or the dialog container itself when the content needs to be read first. The WAI-ARIA Authoring Practices for the modal dialog pattern state that when a dialog opens, focus moves to an element inside the dialog, and that the best placement depends on the content: a static element with tabindex="-1" at the top may suit text-heavy dialogs, while a short prompt usually suits its first control. Fail if focus stays on the page behind, or drops to the browser address bar.

Test 3: Tab and Shift+Tab stay contained

Press Tab repeatedly through the popup. Pass if focus cycles within the dialog: from the last focusable element it returns to the first, and Shift+Tab from the first goes to the last. The same WAI-ARIA guidance notes that Tab moves to the next tabbable element inside the dialog, wrapping at the end, and Shift+Tab wraps at the beginning. Fail if focus ever lands on a link or button on the page underneath while the popup is open — that is the classic sign of a visually modal dialog that is not modal in practice.

Test 4: Escape closes and focus returns

Press Escape. Pass if the dialog closes and focus goes back to the trigger element that opened it, so the next Tab continues from where the visitor left off. Fail if the dialog stays open, or if it closes but focus resets to the top of the page, which leaves keyboard users stranded. Also check that if two dialogs stack, Escape closes only the top one.

Test 5: the close button works without a mouse

Tab until you reach the close control and activate it with Enter or Space. Pass if it is in the tab sequence, has a programmatic label (a visually hidden text label or an aria-label) rather than an unlabeled icon, and actually closes the dialog. The WAI-ARIA pattern strongly recommends that the tab sequence of every dialog include a visible element with the button role that closes the dialog. Fail if the only closure route is a backdrop click, or Escape is disabled with no replacement.

Test 6: fields inside the popup are labelled and constrained

Tab into each field. Pass if every input has a programmatically paired label (clicking the label text moves focus to the field), required fields are announced as required rather than marked with a colour or asterisk alone, and obvious constraints are expressed in HTML. Native attributes are the cheapest first line of defence: MDN's guide to client-side form validation describes required, minlength, maxlength, min, max, step, type and pattern, and notes that marking required fields is required by WCAG accessibility guidelines. It also warns that client-side validation is not a security measure and that server-side checks are still necessary — so a pass here means "the browser can guide the visitor", not "the data is safe".

Test 7: errors are readable and focus stays near the problem

Submit the form with an invalid value using only the keyboard. Pass if an error message appears in text, is associated with the offending field, and focus moves to or stays near that field so the visitor can correct it without hunting. Fail if the message is only a red border, disappears after a few seconds, or focus jumps to the top of the page. Our guides to contact form error messages and HTML form labels for lead forms cover the wording and pairing details this test depends on.

Test 8: check that the background is truly inert

With the dialog open, try to click a link behind it and try to Tab behind it. Pass if the page underneath accepts neither clicks nor focus while the dialog is open. Per the MDN dialog reference, modal dialogs block interaction with other UI and make the rest of the page inert, and the WAI-ARIA pattern notes that content outside an active modal dialog is inert. Also confirm the dialog announces a name — either a visible title referenced by aria-labelledby or an aria-label.

If you are testing a popup built as a native dialog with showModal(), the browser supplies much of this behaviour; if it is a custom overlay, all of it has to be replicated. That distinction shapes the next section.

A logging table and a quick example

Keep the handoff boring and machine-readable. A five-column table is enough:

Test Expected Observed Pass/Fail Notes
2. Initial focus Focus on first field Focus stayed on trigger Fail Screenshot dialog-open-focus.png
3. Tab wrap Last → first Escaped to footer link Fail Chrome 1st run

For the Notes column, record the browser, whether it is desktop or mobile, and the element that received focus (read it from devtools). A useful convention is to keep screenshots only for failures, named by test number, so the fix owner can reproduce the exact state.

Here is a small inspection aid for the Notes column — paste it in the browser console while the dialog is open to see where focus actually is, then remove nothing from the page:

// Console helper: reports the currently focused element.
// Use it during each Tab press of tests 2-5.
const el = document.activeElement;
console.log(el.tagName, el.getAttribute("role"), el.textContent.trim().slice(0, 60));

The code sample is not evidence of behaviour; it only prints what your keyboard presses have already shown.

Fixing failures: panel versus host page

Sort every failure into one of two buckets before anyone edits anything.

Fix in the WidgetJar panel when the problem is popup content or configuration you control there: a missing close affordance, a confirmation step whose behaviour needs adjusting, or which fields appear. Fix on the host page when the problem is the trigger element's markup and label, the surrounding page structure, or a custom overlay your theme adds around the widget. If a field fails Test 6 because the label was written as placeholder text, that is content — correct it in the panel and re-test.

After any change to popup text, fields, timing or trigger, rerun the whole script rather than the single test that failed. Focus behaviour is coupled: moving the initial focus target can break the return behaviour, and adding a field changes the Tab order. Teams that ship popups regularly should also skim the accessible popup checklist and, when they want proof the embed is not hurting page speed, the widget loading performance benchmark — accessibility and performance regressions often arrive in the same release.

Minimum bar before you ship

  • Trigger reachable by Tab, labelled, with a visible focus indicator.
  • Focus enters the dialog on open and returns to the trigger on close.
  • Tab and Shift+Tab wrap inside the dialog; nothing behind it accepts focus.
  • Escape closes the dialog; a labelled close button works with Enter and Space.
  • Fields have paired labels, required state is announced, and errors are text near the problem field.
  • Results logged in the table above, with a screenshot per failure.

If any line is unchecked, the popup is not ready. Keep the script in your release checklist so it runs before every campaign change, not just at launch.

Ready to run it against your own lead capture flow? Build or adjust the popup in the WidgetJar popup widget, install the embed on your staging domain, and put this script next to your release checklist.

  • modal dialog accessibility test script
  • accessibility
  • popups
  • keyboard testing
  • qa

Written by

James Holloway

Web Development Lead at WidgetJar

Keep reading

Popups9 min read

dialog element vs custom modal div for popups

Compare the HTML dialog element with a custom modal div for popups: focus management, keyboard behavior, screen reader support and what to test before launch.

James HollowayNov 8, 2026
Get started

Your next lead is one line away.

Pick a design, make it yours and paste a single script tag. Every widget type is included, and you do not need a credit card to start.

<script src="https://cdn.widgetjar.com/wj-assets/embed.js" defer></script>