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#

Note

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).

Careful

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:

HTML
<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>
CSS
.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.