Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 10

Mastering Z-Index and Stacking Contexts

Recommended resources

Hello! In our last lesson, we delved into the position property, learning how it allows us to control an element's placement by defining its containing block. We saw that using values like absolute, fixed, and sticky often causes elements to overlap. This naturally leads to our next question: how do we control which element appears on top?

You might think the z-index property is the simple answer, but it's governed by a deeper, often misunderstood CSS mechanism. This lesson is dedicated to demystifying that mechanism. Your learning goal is to explain how stacking contexts are formed and how the z-index property behaves within and between them.

Understanding stacking contexts is one of the key conceptual hurdles in CSS. Mastering it will allow you to debug and build complex layered interfaces with confidence, moving beyond guesswork.

We will cover:

  • The default stacking order of elements.
  • The core concept of a stacking context and how z-index is scoped to it.
  • The various CSS properties that create new stacking contexts, sometimes unintentionally.
  • The "trapping" principle: why a child element can never have a higher stacking order than its parent's context.
  • The modern, explicit way to create stacking contexts using isolation.

The Illusion of a Global z-index

Let's start with a common puzzle. An element with z-index: 9999 appears behind an element with z-index: 2. How is this possible?

This happens because z-index is not a global property that ranks all elements on a page. Its value is only meaningful within a specific boundary called a stacking context.

To understand this, it's helpful to use an analogy.

What The Heck, z-index??

The article 'What The Heck, z-index??' by Josh W. Comeau provides an excellent analogy using layers and groups from image-editing software like Figma or Photoshop. This will help you build a solid mental model.

Read the sections 'Layers and groups' and 'Fixing our example'. Focus on the core idea that z-index values are not global but are compared only within the same 'group' or stacking context.

As the article explains, the entire HTML document starts with one root stacking context. When you create new stacking contexts, you create "groups." The z-index of an element only determines its order among its siblings within its current group. The z-index of the groups themselves determines which group appears on top.


Stacking Order Within a Context

Before we even consider multiple stacking contexts, it's important to know the default rendering order within a single context. When elements overlap, the browser follows a specific paint order.

CSS Stacking Order Diagram
This diagram shows the stacking order within a single stacking context. It starts with the background and borders of the context's root element at the bottom (layer 1). It then renders elements with negative z-index (2), followed by in-flow, non-positioned elements (3), floated elements (4), and in-flow, inline-level elements (5). Finally, it renders positioned elements with `z-index: auto` or `0` (6), and then positioned elements with positive z-index values (7), in ascending order.

This diagram illustrates that even without z-index, there's a predictable order. Positioned elements (those with position other than static) are painted on top of non-positioned elements by default.


Creating a Stacking Context

So, what creates these "groups"? While position and z-index are the most common triggers, several other properties create stacking contexts, often as a side effect of enabling GPU-accelerated rendering for performance.

A new stacking context is created on an element when:

  1. It is the root element (<html>).
  2. It has position set to absolute or relative and a z-index value other than auto.
  3. It has position set to fixed or sticky.
  4. It is a child of a flex 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, or clip-path value other than none.
  7. It has isolation set to isolate.

The underlying reason for properties like transform and opacity creating stacking contexts is rendering performance. The browser promotes the element to its own layer (sometimes called a compositor layer) to be processed by the GPU. This isolates its rendering, preventing costly repaints of the entire page during animations. A side effect of this optimization is the creation of a new stacking context.

The following video demonstrates how properties other than position can unexpectedly create stacking contexts and affect z-index.

Solve your z-index issues | z-index and stacking context explained

In this clip from 'Solve your z-index issues,' Kevin Powell shows how properties like transform and opacity create new stacking contexts, which can then enable z-index to work on those elements.

Watch from 06:17 to 08:29. Notice how applying transform or opacity changes the stacking order and allows the use of z-index even without a position property.


The "Trapping" Principle: The Golden Rule of Stacking Contexts

This brings us to the most critical concept for solving z-index issues.

An element can never appear above a sibling of its parent's stacking context, no matter how high its z-index is.

In other words, a child is "trapped" by its parent's stacking context. Its z-index only has meaning inside that parent context.

STACKING CONTEXT in CSS, Simplified | Solving Z-INDEX problems

The video 'STACKING CONTEXT in CSS, Simplified' provides a clear, focused explanation of this bounding behavior with helpful diagrams.

Watch the segment from 10:39 to 14:49. This is the most important concept in this lesson. Pay close attention to how a child element with z-index: 1000 is still rendered underneath a different element because its parent has a lower stacking order.

This is the key to debugging z-index problems. If an element isn't appearing on top, the issue is almost always that one of its ancestors has created a stacking context that is lower in the overall stacking order than the element you're trying to appear above.


A Practical Case Study and Debugging

Let's see these principles applied to a real-world scenario.

Solve your z-index issues | z-index and stacking context explained

We'll return to Kevin Powell's video. He starts with a complex problem (a modal appearing underneath a navigation bar and other page content) and then, after explaining the theory, walks through the diagnosis and solution.

First, watch the problem setup from 00:00 to 01:03. Then, skip to the solution from 09:25 to 17:12. As you watch, try to trace the DOM tree in your head and identify which element is creating the problematic stacking context that 'traps' the modal.

As you saw, the fix involved identifying which ancestor created the unintended stacking context and either removing the property that created it or restructuring the HTML.

For a systematic approach to debugging, you can follow this checklist:

  1. Inspect the element that is not stacking correctly.
  2. Confirm its position and z-index are set as expected.
  3. Climb the DOM tree, parent by parent.
  4. Investigate each parent's styles, looking for any property that creates a new stacking context (position+z-index, opacity, transform, etc.).
  5. Once you find the ancestor creating a context, compare its z-index (and context) with the sibling element it's competing against. This will reveal the root of the problem.

The Modern Approach: isolation: isolate

Often, you want to create a stacking context to encapsulate a component's internal layers, but you don't want the side effects of using position or opacity. For this, CSS provides the isolation property.

Setting isolation: isolate on an element does one thing and one thing only: it creates a new stacking context. It is the cleanest and most explicit way to achieve this.

This is especially useful in component-based architectures like React, where you want to create self-contained components whose internal stacking logic doesn't leak out and interfere with other parts of the application.

What The Heck, z-index??

Josh Comeau's article provides a fantastic explanation of isolation, demonstrating its value in creating an 'airtight' React component.

Read the section 'Airtight abstractions with “isolation”'. Note how using isolation: isolate on the component's wrapper div solves the z-index conflict without having to assign it an arbitrary z-index value itself.


Conclusion

You've now moved beyond just using z-index and have learned the underlying system that governs it. This conceptual knowledge is far more powerful than memorizing z-index values.

Key Takeaways:

  • z-index is not global: Its value is only compared against other elements within the same stacking context.
  • Stacking contexts are like groups: They are created by the <html> element, position with z-index, opacity, transform, and other properties, often for performance reasons.
  • Children are trapped by their parent's context: An element's stacking level is ultimately limited by its parent's stacking context. This is the source of most z-index bugs.
  • isolation: isolate is the best practice for explicitly creating a stacking context to encapsulate a component without unintended side effects.

What's Next?

With this deep dive into the cascade, the box model, and positioning complete, you have a solid foundation in the core mechanics of CSS. We're now ready to move on to the powerful layout systems that define modern web design. In the next module, we'll start with Flexbox, exploring how it manages the sizing and alignment of items along a single axis.