Stacking
Stacking
Multiple modals can be open at once. The package keeps a single global stack and routes Esc, overlay clicks, and focus to the topmost layer — without unmounting underlying modals.
One global stack
Every open modal lives in one ordered list. Opening appends, closing removes. The last entry is the global topmost modal.
[ Modal A: default ] ← opened first
[ Modal B: default ] ← opened second (global top)
This list drives all stacking decisions: which modal reacts to Esc, which one owns focus, which one closes when you click outside.
Global topmost vs topmost in group
| Concept | Drives |
|---|---|
| Global topmost — last entry overall | Esc key, overlay click — only the global topmost reacts. |
| Topmost in group — last entry with that group | Which modal is visually "open" inside a <ModalTarget>. |
These differ when groups mix. With a default form open and a confirm dialog on top:
[ Modal A: default ] ← mounted, data-state="closed"
[ Modal B: confirm ] ← global top — reacts to Esc
Pressing Esc closes Modal B. The default target ignores the event.
Underlying modals stay mounted
When a second modal opens on top, the underlying one is not unmounted. Form values, scroll position, and local state are preserved. Its <ModalContent> switches to data-state="closed" so your CSS animates or hides it.
| State | data-state |
|---|---|
| Visible top in its group | "open" |
| Mounted but covered | "closed" |
| Playing exit animation | "closed" (until animation ends, then unmounts) |
Stack swap sequencing
When the visible top within a group changes, the package waits for the outgoing modal's CSS exit animation before promoting the next one. You never see two modals "open" simultaneously mid-transition.
Group "default" Group "confirm"
───────────────── ─────────────────
[ A: open ] (empty)
openModal(ConfirmDialog) → group "confirm"
[ A: closed ] [ B: open ]
(still mounted)
close B
[ A: open ] (empty)
(promoted back)
Each group sequences its own stack independently.
Same-group stacking
Multiple modals in the same group stack inside one <ModalTarget>. Only one is data-state="open" at a time; closing the top promotes the next one back after its enter animation.
openModal(FormDialog)
// from inside FormDialog:
openModal(ConfirmDialog)
Cross-group stacking
Each group has its own <ModalTarget>, so modals from different groups render in separate DOM subtrees with their own overlays. The global stack still coordinates Esc and overlay-click priority across groups.
<ModalTarget> has nowhere to render. Mount one target per group you use.