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

Share & Support

What's included

Features

True stack-based modal management — an array of open modals, not a single global open/closed boolean
Escape key closes exactly one topmost layer per press, never the entire stack at once
z-index for each layer is derived directly from its position in the stack, always staying in sync by construction
Each layer's own backdrop compounds visually with layers beneath it, producing a natural progressive-dim depth cue
Focus moves to the newly topmost modal both when opening a new layer and when closing one back down to a previous layer
Clicking a modal's own backdrop closes just that layer, correctly scoped to the click's originating overlay
Built with a <template> element, cloning fresh markup per modal instance rather than manipulating one shared DOM node

About this UI Snippet

Stacked Modal Manager — Getting Nested Modals Actually Right

Screenshot of the Stacked Modal Manager — Multiple Modals on Top of Each Other, Correctly snippet rendered live

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:

text
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

  1. 1
    Open the first modalA single dialog appears over a dimmed backdrop, with focus moved to its Close button.
  2. 2
    Click "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.
  3. 3
    Press Escape onceOnly the topmost (most recently opened) modal closes — the one beneath it remains open exactly as it was.
  4. 4
    Keep nesting furtherRepeat opening nested modals to see the stack, z-index layering, and focus management all continue to work correctly at any depth.
  5. 5
    Click 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

CONFIRM
Nested confirmation dialogs
A destructive action inside one modal (like discarding changes) opening a confirmation dialog on top, without losing the first modal's state.
WIZARD
Multi-step flows with detours
A form modal that needs to open a secondary "add new item" modal mid-flow, then return to exactly where the user left off.
ADMIN
Admin tools with drill-down detail modals
Clicking a row in one modal's list to open a detail modal on top, common in admin and support tooling.
HELP
Contextual help overlays
Opening a help or definition popup from within an already-open settings or configuration modal.

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.