Question presented to candidate: "You already have a basic modal that renders through a portal and closes on Escape and overlay click. Our accessibility audit failed it: keyboard users can Tab out of it, screen readers still read the page behind it, the page scrolls under it, and a confirmation opened from inside it breaks everything. Make it pass."
Requirements:
- Focus trap: Tab and Shift+Tab cycle inside the top-most dialog, skipping disabled and hidden controls; focus that somehow lands outside is pulled back in.
- Initial focus on a marked element (or the first tabbable, or the dialog); on close, focus returns to whatever was focused before it opened.
role="dialog"witharia-modal="true"and a label; everything outside the dialog is madeinertwhile it is open.- Body scroll lock while any dialog is open, with scrollbar-width compensation so the page does not shift, and a reference count so stacked dialogs unlock only when the last one closes.
- Stacked dialogs: a confirmation opened from inside the editor; Escape and the trap apply only to the top-most one; closing it returns focus inside the editor.
What a strong answer should cover:
- The order of operations on open (remember focus, inert, lock, move focus) and its exact reverse on close, including why
inertmust be removed before focus is restored. - A tabbable query that excludes disabled,
tabindex="-1", inert and invisible elements, computed on each Tab press. - A layer stack so only the top dialog reacts to keys, and why React portal events bubbling through the component tree make this necessary.
- Scroll lock with
overflow: hiddenonbody, padding compensation frominnerWidth - clientWidth, and a shared counter. - What the native
dialogelement withshowModal()gives you for free, and what it does not (scroll lock).
Clarifying questions expected:
- "Can dialogs stack, and should Escape close all of them or only the top one?"
- "Which browsers do we support? (
inertneeds a 2023-era browser.)" - "Is iOS Safari in scope? Its scroll locking has extra quirks."
Code / implementation expected: Yes. A framework-free helper for trap, inert and scroll lock, a modal component that uses it, and a demo with a nested dialog.