Stacking Contexts: isolation, will-change, and GPU Layers
- Last updated
- Prerequisites:
- CSS positioning and z-index basics
- Understanding of the box model and paint order
- css
- stacking-context
- isolation
- will-change
- compositor
- gpu
- z-index
Read at your depth
The practical view
A stacking context is a group of elements painted together as a unit, in a fixed order relative to siblings. It is created by positioning + z-index, opacity < 1, transform, filter, mix-blend-mode, will-change, contain, and isolation: isolate. Inside a context, z-index only competes with elements inside the same context. To contain a sub-tree's z-index battles, use isolation: isolate on a parent — it creates a fresh context without needing z-index. This is the modern, predictable way to scope stacking instead of fighting z-index: 9999 wars.
The same idea in other frameworks
React components wrap modals/drawers in a portal to a top-level stacking context, relying on the same paint-order rules.Angular overlay/CDK portals mount to the document body to escape component stacking contexts for z-index control.Vue transition wrappers often add transform for animations — which unintentionally creates stacking contexts; beware.Svelte transitions animate opacity/transform, creating stacking contexts and potentially GPU layers during the transition.Legacy vs modern
Fighting z-index wars vs containing them with isolation
Global z-index escalation is fragile; isolation: isolate scopes the stacking battle to a subtree so inner values cannot leak outward.
.modal { z-index: 9999; } /* ❌ OLD: global arms race — the next overlay needs 10000 */.overlay-root { isolation: isolate; } /* ✅ NEW: fresh stacking context, no z-index needed */Interview gotchas
Context
Interviewers ask this to see whether you understand that z-index is scoped by stacking contexts, not a global priority number — the classic production mystery.
The mechanical answer
z-index only orders elements within the same stacking context. If any ancestor of your element creates a stacking context (position + z-index, opacity < 1, transform, filter, will-change, contain, mix-blend-mode, isolation), then your huge z-index competes only against siblings inside that ancestor's context. The ancestor itself is then painted at its own z-index among ITS siblings. So a modal with z-index: 999999 inside a transformed card is trapped: the whole card sits at whatever level the card's context has. Fix: move the element to a top-level context (portal/body) or remove the creating property from ancestors, or contain with isolation at a deliberate root.
Trap
The naive answer is 'z-index is broken' or 'just use a bigger number'. Bigger numbers only matter within the same context. Another trap: assuming opacity: 0.99 or a tiny transform are harmless — they create stacking contexts too, which silently traps descendants. Candidates who trace the ancestor chain to find the context-creating property show real debugging skill.
Context
This tests the compositing model — that will-change is a promise that promotes the element to its own layer with real GPU memory cost, not a performance silver bullet.
The mechanical answer
will-change tells the browser to promote the element into its own composited layer before the property actually changes, so the eventual animation (transform/opacity) runs on the compositor thread without re-rasterizing on the main thread. Each promoted layer is a separate texture in GPU memory, with its own compositing cost. Overusing will-change on many elements (or on huge ones) bloats GPU memory and can cause layer explosion, slow down first paint, and increase memory pressure on low-end devices. Modern browsers also auto-promote layers for running animations, so will-change is best reserved for a small number of known, imminent animations.
Trap
The naive answer is 'will-change makes everything faster'. It is a trade-off: layers cost memory and compositing time, and an idle will-change layer keeps being re-rasterized if content changes. Another trap: thinking will-change creates a stacking context — it does (like transform), so adding it changes paint order and can trap z-index children. Candidates who mention GPU memory, layer limits, and auto-promotion show real understanding.