CSS Nugget #3: z-index problems and local stacking context

HTML elements can optionally have relative, absolute and fixed positions. Absolute positioning adjusts the position of an element with respect to its nearest parent container which has relative position. If there are no parents with relative position, an element with absolute position is placed within the browser window using the top, left, right or bottom properties. An element with fixed position is always positioned within the browser window. When we position elements, there is a chance of elements to overlap. The browser resolves how to display the elements using the z-index property. Enough of text, let us get down to code.

Understanding z-index

Try the following example in CodePen. Let’s add some HTML.

<div id="container">
  <div class="inner-1">inner 1</div>
  <div class="inner-2">inner 2</div>
</div>

We position the container as relative and the inner div elements as absolute. Add the following styles.

.container {
  margin: 100px;
  width: 500px;
  height: 500px;
  background-color: #999;
}

.inner-1 {
  width: 200px;
  height: 200px;
  position: absolute;
  top: 100px;
  left: 100px;
  background-color: #0ff;
}

.inner-2 {
  width: 200px;
  height: 200px;
  position: absolute;
  top: 200px;
  left: 200px;
  background-color: #ff0;
}

With the above styles, the elements appears like so.

Overlapping elements

The second div element overlaps the first. Since it appears last, the yellow box appears over the blue box. By specifying a z-index to the blue box, we can make it appear above the yellow box.

.inner-1 {
 ...
 z-index: 1;
}
z-index = 1 for blue

By specifying a z-index for blue box, we make it appear before the yellow box. But what is the z-index for the yellow box? By default, all absolute or fixed position elements have a z-index of auto. This is equivalent to setting a z-index of 0. But there is a subtle difference. Setting z-index of auto does not create a local stacking context.

Understanding local stacking context

Sometimes, we set a higher z-index to an element but it does not seem to work. Some other element with lower z-index seems to appear above it. How does it happen? This may happen if we inadvertently create a local stacking context. Whenever we specify a z-index, it creates a local stacking context. All its child elements will have the parent’s z-index. So, even if we specify a higher z-index to an individual child element, some other element with a lower z-index may appear on top of it. Let’s simulate this with an example.

We will add two more child elements in our existing CodePen example.

<div id="container">
  <div class="inner-1">inner 1
    <div class="inner-3">inner 3</div> 
  </div>
  <div class="inner-2">inner 2
    <div class="inner-4">inner 4</div> 
  </div>
</div>

Specify styles for inner-3 and inner-4 classes.

.inner-3 {
  width: 100px;
  height: 100px;
  position: absolute;
  top: 50px;
  left: 50px;
  background-color: #f00;
}

.inner-4 {
  width: 100px;
  height: 100px;
  position: absolute;
  top: 50px;
  left: 50px;
  background-color: #0f0;
}

Both the inner elements have position set to absolute.

More overlapping elements

Green is above yellow because both have z-index = auto and Green appears after yellow as its child.

Red inherits the z-index of 1 from its blue parent. So, it appears above blue. And since blue has a z-index of 1, it appears above both yellow and green.

Let’s change this around by setting a z-index of 2 to yellow.

.inner-2 {
  ...
  z-index: 2;
}
z-index of 2 to yellow

Yellow appears above red and blue because it has the higher z-index. So far, so good. Now, let’s simulate the problem by specifying a higher z-index of 3 to red.

.inner-3 {
  ...
  z-index: 3;
}

Though we specify a higher z-index for red, nothing happens. Because red already inherits the z-index of blue which is 1. Every parent which specifies a z-index creates a local stacking context for its children. And children cannot have a higher z-index.

To fix this problem, remove the z-index of blue. This makes the z-index as auto. So, it removes the local stacking context created for the red element.

.inner-1 {
  width: 200px;
  height: 200px;
  position: absolute;
  top: 100px;
  left: 100px;
  background-color: #0ff;
}
Red appears above Yellow

Now, we get the desired result. Red appears above yellow as it has a z-index of 3 as against a z-index of 2 for yellow.

Summary

Sometimes, we increase the z-index of an element (A) and it does not appear above an element (B) with a lower z-index. In such cases, the element with higher z-index may have a parent (C) with z-index specified. And the parent’s z-index (C) is lower than that of the other element (B). To fix it, remove the parent’s z-index (C). Or specify a higher z-index for the parent (C) rather than the original element (A).

Update

Specifying a z-index is not the only property that creates a local stacking context. But setting opacity, transform or position: fixed creates a local stacking context. How do we pictorially understand this?

Stacking context

In the above picture, A and B create a new local stacking context for child elements – D and E respectively. Since, they are in their own stacking context, setting z-index on them makes sense. Further, since we define opacity for B and no z-index, both B and E have z-index of 0. The elements A, B, C and F all belong to the global stacking context. So, setting z-index on F can make it appear below or above the rest of the elements. In short, all elements belong to global stacking context. However setting one of z-index, opacity, fixed position or transform properties in any of the element will cause all its child elements to belong to a new local stacking context and inherit its z-index. Phew, so complicated.

Related Posts

Leave a Reply

Your email address will not be published.