The z-index property might seem simple, but it's not just about using a positive or negative integer.

There's a lot more going on under the hood, and some quirks can cause problems when you're working with it.

In this article, we're going to dive deep into the z-index property and learn how it really works.

So grab a cup of coffee or tea, and be ready with a pen and paper 😉

z-index controls the stacking order and creates the stacking context. So first let's understand what these terms mean.

Stacking Order

When a browser is rendering your web page elements along the z-axis it has to choose which element to draw on the canvas first.

And the order of those elements is called stacking order.

Before we move further and talk about stacking order with the z-index property, let's understand what default stacking looks like:

  1. backgrounds and borders of the root element.
  2. non-positioned block elements (in order of appearance)
  3. non-positioned floating elements (in order of appearance)
  4. inline elements (in order of appearance)
  5. positioned elements (in order of appearance)

Root element Background and borders represent the lowest level of the stack. Positioned elements represent the highest level of the stack.

Note: a positioned element is an element that's either relative, fixed, absolute, or sticky – anything other than static.

A non-positioned element is below the inline element and positioned element. If we change the code and add relative position into the second div then it will hide the inline element.

How the z-index property works

The z-index property is used to change the default stacking order of elements.

The z-index can take one of 3 values:

  1. auto: The stack order of the element is the same as that of its parent element. This is the default value.
  2. integer: it can be a positive or negative number.
  3. inherit: sets the value the same as the parent element's value.

z-index only works with a positioned element. (Well, this is not completely true because there are some edge cases too – but we will discuss those below.)

The highest positive z-index value means the one nearest to the user, and the lowest negative z-index value means the one farthest from the user.

The above codepen example has the same code as the default stacking order example, but I just changed a single line in the 3rd element code.

z-index: -1;

I added a negative z-index, so it hides positioned elements below the non-positioned element.

When we specify the z-index on the positioned element, it creates a stacking context.

The simple z-index property gets a bit complicated with the introduction of the stacking context.

Stacking context

Groups of elements with a common parent that move forward or backward together in the stacking order make up what is called a stacking context. (Official Definition from w3.org.)

The root element (HTML) forms the root stacking context. Other stacking contexts are generated by any positioned element (including relatively positioned elements) having a computed value of ‘z-index’ other than ‘auto’. Stacking contexts are not necessarily related to containing blocks.

I hope this definition makes sense.

The HTML tag forms the root stacking context and by default, all elements belong to this stacking context.

But by using positioned elements and a z-index other than the "auto" value, we can create a local stacking context. That element is known as the root element in the local stacking context.

The parent element's children belong to that local stacking context and they move backward and forward together in the stacking order.

Why we need to know stacking context

So why should you learn about all of these things if you can simply use the z-index by giving positive or negative values?

Let me give you a simple problem:

We have 3 divs with z-index values of 1, 999, and 2. But the confusing part is the second div which has a 999 z-index value (the highest value) – and it is still not able to come in front of the third div.

This is where our understanding comes in handy: this all happens because of stacking context.

Let's find out why.

Taka a look at the HTML:

   <main>
      <div class="first">1</div>
      <div class="second">999</div>
   </main>
   <div class="third">2</div>

We have three divs with class names of first, second, and third. The first and second div are wrapped into the main tag.

Here's the CSS:

.first {
  z-index: 1;
}

.second {
  z-index: 999;
}

.third {
  z-index: 2;
}

You can see that the second div has the highest z-index value. still, it's not able to come in front of the third div.

All this is happening because of this line of code👇

main {
  position: relative;
  z-index: 1;
}

The main element is creating a local stacking context and the first and second div belong to the same local stacking context – but the third div doesn't.

That's why the second div is able to come in front of the first: because it has a z-index value of 999 (higher than the first div).

The main tag and third div are the children of the root element, both having the z-index values.

The main element has a z-index value of 1 and the third has a z-index value of 2. The third has the higher z-index value which is why the third div is able to come in front of the main tag.

I hope you now understand how stacking context works and why you need to know about it.

How to Create Stacking Contexts

We've seen how we can combine positioning (especially relative and absolute) with the z-index to create a stacking context – but that's not the only way! Here are some others:

  1. using position fixed or sticky (No z-index needed for these values!)
  2. Adding a z-index value to a child within a display: flex or display: grid container
  3. adding an opacity value of less than 1

These are some of the common ways to do this, but you can check out the full list here.

I told you earlier that the z-index actually doesn't just work with positioned elements – there are some edge cases.

The z-index property works well with grid and flex container children without specifying positioning.

Check out the below example and code:

This works because adding a z-index value to a child within a grid or flex container forms a stacking context.

Conclusion

I know these things are confusing and a little bit complex.

I hope after reading this article and practicing with the z-index property you understood how things work behind the scenes.

But if you are still confused then you can contact me here.