Stacked Modal Manager — Multiple Modals Layered Correctly, One Escape Press Per Layer
Stacked Modal Manager — Multiple Modals on Top of Each Other, Correctly · Modals · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Stacked Modal Manager — Getting Nested Modals Actually Right

Most modal implementations assume exactly one modal is ever open at a time — a single boolean, a single overlay, a single Escape handler. The moment a real product needs to open a confirmation dialog *from inside* another modal (a common pattern: edit form → "are you sure you want to discard?"), that single-modal assumption breaks down. This snippet models modals as a genuine stack, where every operation is defined relative to "the top of the stack" rather than a single global open/closed flag.
Why a stack (array), not a counter or a boolean
stack holds the actual DOM overlay elements, in the order they were opened — not just a count of "how many modals are open." This distinction matters because closing needs to know *which specific modal* to remove (always the last one pushed, i.e. stack.pop()), and Escape handling needs to know *which one* to focus afterward. A simple counter could tell you "2 modals are open" but not which two, or in what order, or which one is currently on top and interactive — the array is what makes every subsequent operation correct.
Escape closes exactly one layer, never the whole stack
The keydown listener calls closeTopModal() — singular, one layer — every time Escape is pressed, rather than clearing the whole stack at once. This is a deliberate UX decision: if a user opened an edit form, then a nested "discard changes?" confirmation on top of it, hitting Escape once should dismiss just the confirmation (returning them to the edit form, still open) — not blow past it and close everything, which would risk discarding unsaved state the confirmation dialog existed specifically to protect.
Z-index stacking mirrors the array order automatically
Each overlay's z-index is set to 50 + depth, where depth increases by one for every nested modal — so the array order and the visual stacking order are always kept in lockstep by construction, not by a separate manually-maintained z-index counter that could drift out of sync with the actual stack array. Because each overlay also renders its own semi-transparent backdrop, layering three modals produces a naturally progressively-darker combined background with zero manual opacity math — each layer's own dimming simply compounds visually with the ones beneath it.
Focus always returns to the correct remaining layer, not a stale one
closeTopModal() doesn't just remove the top overlay — after popping it off the array, it calls topModal() again to find whatever is now the new top (which could be another modal, or nothing if the stack is now empty) and moves focus there. This guarantees that closing a triple-nested modal always leaves keyboard focus somewhere sensible and *current* — never on a reference to a modal that was itself already closed earlier, and never simply dropped onto the page body when a modal is technically still open beneath it.
Build with AI
Build, Understand, Optimize, and Extend It With AI
Ask an AI assistant to explain why Escape closing only the topmost modal (rather than the whole stack) is the correct UX choice for nested confirmation flows, and to trace through what focus ends up on after closing a modal at each possible stack depth. It's also worth asking for a version that also implements a proper focus trap within each individual layer (so Tab cycles only within the topmost modal's own focusable elements), or one that limits maximum stack depth and shows a warning instead of allowing unbounded nesting.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a stacked/nested modal manager in HTML, CSS, and vanilla JavaScript — no external library.
Requirements:
- A way to open a modal, and from inside that modal, a button to open another modal on top of it, supporting at least three levels of nesting.
- Track open modals as an ordered stack (array) of DOM elements, not a single boolean or counter — every operation (opening, closing, Escape handling, focus management) must be defined in terms of the top of that stack.
- Pressing the Escape key must close exactly one modal — the current topmost layer — per press, never the entire stack of open modals at once.
- Clicking directly on a modal's own backdrop (not a modal underneath it) must close just that topmost layer, using the same single-layer-close logic as Escape.
- Each modal layer must render its own semi-transparent backdrop, so that with multiple modals open, the combined visual dimming naturally compounds and gets progressively darker with each additional open layer — no manual/shared opacity value calculation needed.
- Each layer's stacking (z-index) must be derived directly from its position in the stack at the moment it opens, so visual layering always stays correctly in sync with the actual open order.
- When a modal opens, move keyboard focus into it. When a modal closes, move focus to whatever is now the new topmost remaining modal (or leave it on the page if the stack becomes empty) — never to a reference to an already-closed modal.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Step by step
How to Use
- 1Open the first modalA single dialog appears over a dimmed backdrop, with focus moved to its Close button.
- 2Click "Open next modal" from inside itA second modal opens on top, with its own backdrop compounding visually with the first for a progressively darker overall dim.
- 3Press Escape onceOnly the topmost (most recently opened) modal closes — the one beneath it remains open exactly as it was.
- 4Keep nesting furtherRepeat opening nested modals to see the stack, z-index layering, and focus management all continue to work correctly at any depth.
- 5Click outside a modal's contentClicking directly on a modal's own backdrop (not a modal further underneath) closes just that topmost layer, same as pressing Escape.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Closing and focus management both need to know exactly WHICH modal is on top, not just how many are open in total. The array (stack) holds the actual DOM elements in open order, so operations like "close the top one" or "focus whatever is now on top" have a concrete, unambiguous element to act on.
No — it closes exactly one layer, the topmost one, per press. This is deliberate: a nested confirmation dialog exists specifically to be seen and responded to before returning to the modal beneath it, so Escape skipping past all of them at once would undermine that purpose.
Each overlay's z-index is computed directly from its depth in the stack at the moment it's created (50 + depth), so the visual stacking order is always derived from — and therefore always consistent with — the actual array order, rather than a separately maintained counter that could drift out of sync.
closeTopModal() re-checks the stack after removing the top overlay and moves focus to whatever is now the new top (another modal, if one remains, or nowhere if the stack is empty) — never to a stale reference to a modal that no longer exists.
Not with the current implementation — closing always operates on the top of the stack, matching how nested dialogs conventionally behave (you resolve the innermost prompt before returning to what's beneath it). Closing out of order would require a different data structure and UX pattern.
Each nested modal is a fully independent instance with its own event listeners and content — cloning a <template> for every openModal() call keeps each layer's DOM and listeners cleanly isolated from every other layer, rather than trying to repurpose one shared node for multiple simultaneous, independently-closeable modals.