What actually creates a stacking context?
A field guide to the twenty-odd declarations that quietly reorganise your paint order.
- Level
- ENGINEER
- Read time
- 07 min
- Experiment
- Available
- Type
- INTERACTIVE
The question
Most developers can name two: position with z-index, and opacity. The real list is much longer, and several entries look completely harmless.
Hypothesis
Anything that forces the browser to treat a subtree as one composited group establishes a stacking context.
Method
Laboratory available
The instrument for this investigation runs in the laboratory, where the controls, the live model and the observation log share one workstation.
Enter the laboratoryToggle declarations on the wrapper element. The bench reports whether a stacking context now exists and why.
What we observed
Three families emerge. Group semantics (opacity, filter, blend, mask) need a flattened subtree. Compositing hints (transform, will-change, backdrop-filter) promote the subtree toward its own layer. Containment (contain: paint, content-visibility) promises the browser that nothing paints outside the box, a promise only meaningful for a well-defined group.
Why it happens
| Declaration | Family | Notes |
|---|---|---|
| position: relative/absolute + z-index ≠ auto | classic | The one everybody knows |
| position: fixed / sticky | classic | Always, regardless of z-index |
| opacity < 1 | group | Even 0.999 |
| transform ≠ none | compositing | Including translateZ(0) |
| filter / backdrop-filter ≠ none | group | Even blur(0px) |
| mix-blend-mode ≠ normal | group | Needs a backdrop group |
| isolation: isolate | group | Its only job |
| will-change: <a context-creating property> | compositing | Creates it eagerly |
| contain: paint / layout / strict / content | containment | Paint containment implies a context |
| content-visibility: auto/hidden | containment | Implies containment |
| flex/grid child with z-index ≠ auto | classic | No positioning required |
| mask / clip-path / mask-image ≠ none | group | Group must be flattened to clip |
| element in the top layer (dialog, popover) | special | Painted above everything else entirely |
Further research
will-change deserves a warning of its own. It is a hint with a cost: the browser may promote the element to its own compositor layer and hold the memory for as long as the declaration is there. Applying will-change: transform to everything is one of the more reliable ways to make a page slower while believing you optimised it.