FrontendPrep
cssMedium

The CSS Stacking Context and z-index

Understand the CSS Stacking Context and why z-index sometimes doesn't work as expected. Learn how to debug z-index issues and manage layer order in web layouts.

The CSS Stacking Context and z-index

A very common question asked in frontend interviews to evaluate practical layout knowledge is:

"Why isn't my z-index working? Explain what a Stacking Context is and how it affects the rendering order of elements."

Most developers know that a higher z-index value brings an element closer to the viewer. However, many encounter a situation where an element with z-index: 9999 is somehow hidden behind an element with z-index: 1.

This is where understanding the Stacking Context becomes crucial.


1. What is a Stacking Context?

The Stacking Context is a three-dimensional conceptualization of HTML elements along an imaginary z-axis relative to the user. Elements occupy this space in order of priority based on element properties.

When a stacking context is formed on an element, that element becomes the root of a new local z-axis for all its children.

The Key Rule

[!IMPORTANT] The z-index values of child elements only have meaning within their parent's stacking context.

If Element A and Element B are in different stacking contexts, and Element A's stacking context is below Element B's stacking context, then no matter how high you set the z-index of a child inside Element A, it will never appear above Element B.


2. The 7 Layers of Stacking Order (Within a Single Context)

Before discussing how stacking contexts are created, we must understand how a browser orders elements within a single stacking context by default.

Here is the exact stacking hierarchy (from back to front):

The 7 Layers of Stacking Order (Back to Front)

Layer 7: Positioned elements (z-index > 0)Top Layer (Closest to user)
Layer 6: Positioned elements (z-index: 0 or auto)
Layer 5: Non-positioned Inline elements (text, inline nodes)
Layer 4: Non-positioned Block elements (div, p, sections in normal flow)
Layer 3: Positioned elements (z-index < 0)
Layer 2: Stacking Context Parent's background & bordersBase

💡 Elements on higher layers will always cover elements on lower layers, regardless of their source code order.


3. How is a Stacking Context Created?

A common misconception is that only position: absolute or position: relative combined with z-index creates a stacking context. While true, many other modern CSS properties trigger a new stacking context.

An element forms a new stacking context if any of the following are true:

  1. It is the root element (<html>).
  2. It has position: absolute or relative AND a z-index value other than auto.
  3. It has position: fixed or sticky.
  4. It is a child of a Flexbox or Grid container AND has a z-index value other than auto.
  5. It has an opacity value less than 1.
  6. It has a transform, filter, perspective, clip-path, or mask property other than none.
  7. It has isolation: isolate.
  8. It has a will-change value specifying any property that would trigger a context on its own (like transform or opacity).

[!WARNING] Applying a seemingly unrelated property like opacity: 0.99 or transform: scale(1) to a parent element will instantly create a new stacking context, potentially trapping all its children behind other elements on the page!


4. The Classic z-index Trap (Example)

Consider the following HTML and CSS:

<div className="parent-a">
  <div className="child-a">Child A (z-index: 9999)</div>
</div>
 
<div className="parent-b">
  <div className="child-b">Child B (z-index: 1)</div>
</div>
.parent-a {
  position: relative;
  z-index: 1; /* Creates Stacking Context A */
}
 
.child-a {
  position: absolute;
  z-index: 9999;
}
 
.parent-b {
  position: relative;
  z-index: 2; /* Creates Stacking Context B */
}
 
.child-b {
  position: absolute;
  z-index: 1;
}

What happens here?

Even though child-a has a massive z-index of 9999, it will render behind child-b (which only has a z-index of 1).

Here is a visual map of how the browser evaluates this:

Visual Stacking Hierarchy of the Trap

Parent B (Stacking Context Level: 2)Wins & Layers Higher

Child B (z-index: 1 within Context B)

Parent A (Stacking Context Level: 1)Layers Lower
Child A (z-index: 9999)Trapped inside Context A!

Why does this happen? Because parent-a has a z-index of 1, and parent-b has a z-index of 2. The browser compares the parents first. Since parent-b is higher than parent-a, everything inside parent-b will render on top of everything inside parent-a. The z-index: 9999 of child-a only means it is on top of other things inside parent-a.


5. How to Debug and Fix Stacking Issues

If your element is trapped behind another, follow these steps to diagnose and repair the bug:

  1. Check the parent chain: Walk up the DOM tree of the hidden element and look for any parents with position, opacity, transform, filter, or grid/flex layouts.
  2. Use the isolation property: If you need to intentionally trap the z-index of child elements to prevent them from bleeding out over other parts of your app, use isolation: isolate on the container. This is cleaner than hacking it with z-index: 1; position: relative.
  3. Use a Portal: In React/Next.js, if you have a Modal or Tooltip that needs to break out of all stacking contexts and render on top of the whole page, use a Portal (createPortal) to append it directly to document.body or #portal-root.

Key Takeaways

  • z-index is not global. It operates locally within its parent's stacking context.
  • Many CSS properties besides position (like opacity, transform, and filter) create new stacking contexts.
  • Sibling stacking contexts are compared first. If a parent is "lower" than another parent, its children cannot break out to overlay the higher parent.
  • Use isolation: isolate to purposefully create stacking contexts, and Portals to escape them.

Share this Resource

Help other developers level up by sharing this study guide.

More Technical Questions

Expand your mastery. Deep dive into other frontend interview challenges in this category.