Layering bugs look like visual chaos, but they are determined by machine-readable inputs: computed styles and DOM ancestry. A screenshot can show that elements overlap; it cannot show which stacking context owns each element. The primary computable question is what agents can derive from the DOM, while the secondary perceptual question is what a screenshot can reveal: symptoms, not causes. This mirrors WCAG 1.4.10 reflow check, where the check needs layout facts, and scroll-driven animation verification, where a single frame is not proof.
The Screenshot Shows the Symptom, the DOM Shows the Rule
A screenshot can show that two elements overlap, but which one wins depends on stacking contexts, defined by MDN as a “three-dimensional conceptualization of HTML elements along an imaginary z-axis” (MDN Stacking context guide).
The image gives final paint order. The DOM gives the rule that produced it. An element with a large z-index may still be hidden if its ancestor created a lower context. A pixel-only agent can flag the collision; a DOM-aware agent can name the owner.
What a Stacking Context Actually Is
A stacking context stacks its elements independently of everything outside it and is treated atomically by its parent, per MDN (MDN Stacking context guide).
That means a context is a local coordinate system for layering. Children can fight inside it, but the whole context participates in the parent context as one unit. Once you identify context boundaries, you can compare only the relevant units and ignore numbers that are not peers.
Why This Is Computable — and Why Humans Find It Hard
MDN itself says z-index behavior in complex hierarchies is “hard to understand or predict” — which is precisely the kind of rule-heavy, state-light problem agents can evaluate systematically (Understanding z-index guide).
Humans often remember “higher number wins” and miss context traps. The rule set is finite: computed properties plus ancestry. An agent does not need intuition; it needs a parser, a style resolver, and a comparison procedure.
The Full List of Stacking-Context Creators (the Rule Set)
MDN enumerates the creators exhaustively: root, positioned elements with non-auto z-index, position: fixed or sticky, container-type: size|inline-size, flex/grid items with non-auto z-index, opacity < 1, mix-blend-mode != normal, transform/scale/rotate/translate/filter/backdrop-filter/perspective/clip-path/mask/mask-image/mask-border values other than none, isolation: isolate, will-change naming such a property, contain: layout|paint|strict|content, top-layer/::backdrop, and animated creators with animation-fill-mode: forwards (MDN Stacking context guide).
The Sneaky Ones — opacity, transform, filter
opacity less than 1, mix-blend-mode other than normal, and transform/scale/rotate/translate/filter/backdrop-filter/perspective/clip-path/mask/mask-image/mask-border values other than none create a context (MDN Stacking context guide).
These are common because they are visual, not positional. A faded card or a hover transform can change stacking ownership.
The Intentional Ones — isolation and contain
MDN says isolation: auto creates a new stacking context only if another property requires it, while isolation: isolate must create one; isolation is especially helpful with mix-blend-mode and z-index (MDN isolation property).
contain: layout, paint, strict, or content creates a new stacking context (MDN contain property).
will-change can create a context up front when it names a property that would create one, and MDN advises using it as a last resort (MDN will-change property).
Flex and Grid Items Break the “positioned only” Mental Model
A flex or grid item with z-index other than auto creates a stacking context even without position (MDN Stacking context guide).
This is where mental models fail. The rule is not “positioned only”; it includes flex/grid and other creators.
The Classic Trap: z-index 6 Losing to z-index 5
In MDN’s worked example, a SECTION #4 with z-index 6 inside an ARTICLE #3 with z-index 4 renders UNDER an ARTICLE #1 with z-index 5, because the 6 is only compared within its parent context (MDN Stacking context guide).
| Question | Screenshot perception | DOM computation |
|---|---|---|
| What overlap shows | two painted regions intersect | element order, context boundaries, computed properties |
| Which context owns an element | not visible | nearest ancestor that passes stacking-context creator rules |
| Why element A is above B | inferred from final pixels | z-index values and context chain compared locally |
| Reproducibility | may change with viewport, animation, repaint | deterministic from computed styles and DOM ancestry |
| Fix verification | visual pass/fail only | re-run rules and predict new render order |
The lesson is not that z-index is broken. It is that z-index values are local. ARTICLE #3 with z-index 4 forms a context; SECTION #4 with z-index 6 lives inside it. ARTICLE #1 with z-index 5 is compared against ARTICLE #3, not against SECTION #4. A DOM reader predicts the 4-6 versus 5-0 outcome; a screenshot reader only sees the failure.
The Computable Rules
Every layering question reduces to a pass/fail check on computed styles plus DOM ancestry, because z-index integer values establish local contexts whose descendants are never compared with outsiders (MDN z-index property, CSS2 z-index spec).
- R1 — Context creation test: Apply the creator list above to each element, including root, fixed/sticky, top-layer/
::backdrop, and animated creators withanimation-fill-mode: forwards(MDN Stacking context guide). - R2 — Context chain build: Walk DOM ancestors from element to root; each ancestor passing R1 marks a context boundary. Pass if the chain is complete.
- R3 — Peer comparison test: Two elements can only be compared if their nearest common ancestor-context is the same. If not, verdict: not comparable.
- R4 — z-index validity test:
z-indexapplies to positioned elements and flex/grid items; a non-positioned, non-flex/grid element’sz-indexis not effective (MDN z-index property). - R5 — auto vs integer test:
z-index: autois level 0 and does not establish a new context; an integer does (MDN z-index property). - R6 — No-z-index fallback test: With all relevant
z-indexvalues auto or absent, predicted order is root background/borders, then non-positioned descendants in HTML order, then positioned descendants in HTML order (MDN stacking without z-index). - R7 — Isolation intent test:
isolation: isolatepasses as deliberate context creation, useful withmix-blend-modeandz-index(MDN isolation property).
How an Agent Verifies a Layering Claim
An agent walks the DOM from each element up to the root, reading computed position, z-index, opacity, transform, scale, rotate, translate, filter, isolation, contain, and will-change to build each element’s context chain (MDN Stacking context guide).
- Capture computed styles, not authored CSS, for each candidate and ancestor:
position,z-index,opacity,transform,scale,rotate,translate,filter,backdrop-filter,perspective,clip-path,mask,mask-image,mask-border,mix-blend-mode,isolation,contain,container-type,will-change,display, andanimation-fill-mode. - Run R1 on each node to flag context creators.
- Build each disputed element’s ancestor context chain with R2.
- Run R3 on the pair. Same context: compare
z-index. Different contexts: compare the contexts’ ownz-indexat their common parent. - Apply the MDN example logic to explain any “high z-index, low render” outcome.
- After a proposed fix, mutate the DOM or styles, then re-run R1–R3 and re-predict the order.
A verification artifact should include the computed style snapshot, the context chain, the failed rule, and the predicted before/after order. This is the same posture as APCA contrast linter: the claim is checked against a computable rule, not a visual opinion.
What Happens With No z-index at All
With no z-index, the order is root background, then non-positioned descendants in HTML order, then positioned descendants in HTML order — so static elements draw below positioned ones even when later in markup (MDN stacking without z-index).
This is the default contract. Removing z-index does not remove layering; it changes the comparison from integer values to document order and positioning status. An agent that treats “no z-index” as “no stacking” will mispredict overlaps.
Baseline Reality Check
The contract is stable: MDN marks z-index Baseline since July 2015, isolation and will-change since January 2020, and contain since March 2022 (MDN z-index property, MDN isolation property, MDN will-change property, MDN contain property).
| Property | Baseline since | Creates context when |
|---|---|---|
| z-index | July 2015 | integer on positioned/flex/grid item; auto does not |
| isolation | January 2020 | isolate; auto only if another property requires it |
| will-change | January 2020 | names a property that creates a context, up front |
| contain | March 2022 | layout, paint, strict, content |
Baseline dates are support facts, not performance facts. They tell an agent that the rules are broadly documented, not that every implementation detail is cheap.
Limitations — Where the Contract Gets Fuzzy
MDN documents spec behavior, not measured real-world sites, and animated properties can create a stacking context only mid-animation, which a static DOM snapshot can miss (MDN Stacking context guide).
- Top-layer elements, such as fullscreen or popover, sit outside normal document comparison.
- A screenshot-only agent can detect overlap but cannot attribute cause.
- A static snapshot may miss an animated creator, such as opacity or transform, when
animation-fill-mode: forwardsis involved. - Some teams may still rely on visual inspection for quick triage, but that cannot replace a computed-style contract.
FAQ
Why can a screenshot not prove a z-index fix?
A screenshot records final pixels, not causes. MDN defines stacking contexts as independent layers treated atomically by their parent (MDN Stacking context guide), so a fixed overlap is checked against the element’s computed styles and ancestor chain, not against the next image.
Does z-index always win when the number is higher?
No. MDN’s example shows SECTION #4 with z-index 6 renders under ARTICLE #1 with z-index 5 because SECTION #4 is trapped inside ARTICLE #3, whose z-index 4 forms the parent context (MDN Stacking context guide), so the local number cannot be compared across contexts.
When is isolation preferable to z-index?
MDN says isolation: isolate creates a new stacking context and is especially helpful with mix-blend-mode and z-index (MDN isolation property). Unlike transform or opacity, which create a context as a side effect, isolation states the requirement directly on the component.
What does z-index: auto mean?
MDN says z-index: auto places the element at level 0 without establishing a new stacking context, while an integer creates a local context so descendants are not compared with outsiders (MDN z-index property). This distinction is essential for DOM-based verification.
The Bottom Line
z-index is computable because MDN defines stacking contexts as independent, atomic layers, so agents should verify layering from computed styles and DOM ancestry rather than screenshots (MDN Stacking context guide).
The verdict is simple: demand the DOM. A screenshot can report a symptom; a computed-style contract can predict, explain, and re-check the fix.
How This Guide Was Built
The facts here come from MDN Stacking context guide, MDN z-index property, and CSS2 z-index spec. This review is based on MDN and specification sources; we did not run any of these examples in a browser.
