DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Aslam Shah
Aslam Shah

Posted on • Updated on

Why z-index doesn't work all the time 🀯 ?

If you have struggled with z-index you are not alone.

Look at the code snippet below:

The circle div has a z-index of 99999 but it is still showing under the header which has a z-index of 2. why is this happening? Should circle be on top as it has the highest z-index?

z-index doesn't simply place element on top of lower z-index or no z-index items. There is a logic to it. If you understand this logic, then next time when you add a z-index property, you will know where to put it and why.

Lets dig into it:

z-index is based on the stacking context of the HTML elements. Stacking context simply means how the elements are stacked in the HTML tree whether the elements are in same stacking context or not e.g in the code snippet above the stacking context of header and main is different than circle. We can create new stacking context as soon as we add z-index properties to the elements.

There are also many other ways of creating stacking context. If you want to dig deeper you can read about it here

if we add z-index to header element then it will create a new stacking context comparing main and header. The circle z-index will have no effect as it has its own stacking context inside the main element's context. But if we create a z-index on the circle div then the context will be between it siblings ( if any ).

In order to solve the problem, all we need to do is remove the z-index from the main which will remove the new stacking context. Now it will simply be header and then circle in terms of stacking context.

Related Links:
Stacking Context
Stacking without z-index

I hope you enjoyed this article. Follow for more css tips and tricks.

hunzaboy image

Top comments (13)

Collapse
peerreynders profile image
peerreynders • Edited on

We can create new stacking context as soon as we add z-index properties to the elements.

Only if:

  • the value isn't auto AND
  • the element also has a position value of absolute or relative OR
    • the element also has display: flex OR
    • the element also has display: grid

The relative in header doesn't seem to have a reason to exist. Once that is removed the problem goes away.

In fact you can clean it up even further.

  • The index: 2 in header can be removed.
  • The index: 1 in main can be removed, eliminating that stacking context. We have to keep position: relative in order for the circle absolute positioning to work.
  • The index: 99999 in circle can be removed. The div will automatically appear on top of main because of HTML source order-later/nested elements will naturally stack on top of earlier ones within the same stacking context (Stacking without the z-index property).

Now the only remaining stacking context is the one from the html root.

This demonstrates that using z-index indiscriminately can cause more problems than it solves - especially as it is responsible for a new stacking context being created on display: flex, display: grid, position: relative/absolute elements.

For Firefox there is a CSS stacking context inspector add-on/extension. (Chrome devtools extension; github)

PS: Block Formatting Contexts are something entirely different.

Collapse
hunzaboy profile image
Aslam Shah Author • Edited on

Thanks for the feedback. I agree, however that was the usecase i was tackling. In essence that z-index will NOT bring the element to the top. Also added more links to the article.

Collapse
jamesthomson profile image
James Thomson

Just wanted to mention that transform will also affect stacking context - which isn't mentioned in the article.

Collapse
peerreynders profile image
peerreynders

The article does link to the stacking context which lists the various conditions under which a new stacking context is created.

I was focusing on the ones that involved z-index in one way or another - i.e. z-index by itself isn't sufficient for creating new stacking context but it can cause one to be created when other specific declarations are already in place.

Thread Thread
jamesthomson profile image
James Thomson

Right, which makes sense. I just thought for the sake of this article/post it's worth mentioning as some may not realise that transforms can affect the context as well.

Taking from ops example, let's say they want a fixed header and the main area to transition during route changes. Some may not expect adding a transform to main will change the stack: codepen.io/getreworked/pen/JjMrMZQ...

Thread Thread
peerreynders profile image
peerreynders

Creating Stacking Contexts hints at why stacking contexts exist in the first place; some property values need to be contained and isolated to keep things manageable.

Interestingly the Incomplete List of Mistakes in the Design of CSS states that tables and overflow: scroll should have created their own stacking contexts as well.

Collapse
jfbrennan profile image
Jordan Brennan • Edited on

The one part of CSS I gave up trying to understand...ugh, I hate z-index! Best piece of advice I got is to not think of z-index as layers, but rather as timing, i.e. when will the browser paint this element.

Collapse
peerreynders profile image
peerreynders • Edited on

Whatever works for youβ€”but I don't understand how that even works as a mental model.

Whenever things get a little more complicated it becomes necessary to locate the relevant stacking contexts to identify the positioned element (flex, grid item) where the z-index needs to be added/modified in order to obscure/reveal the desired portion of the layout.

I find the first hurdle is understanding that z-index has no effect unless the element:

  • is positioned
  • is a flex item (child of a flex container)
  • is a grid item (child of a grid container)

All of which create their own local stacking context (in combination with z-index). However z-index doesn't work on stacking context roots created by other property values, e.g. opacity: 0.9.

The next hurdle is understanding that z-index only coordinates the ordering of stacking contexts within the same parent stacking context.

Back to front ordering within a stacking context:

  • The root element of the stacking context
  • Positioned elements with a negative z-index (along with their children)
  • Non-positioned elements
  • Positioned elements with a z-index of auto (and their children)
  • Positioned elements with a positive z-index (and their children)

For example the stacking context hierarchy for Stacking context example 3

- #document
  - div#container1 (absolute & z-index: 1)
    - div.lev2 (opacity: 0.9)
      - div#container2 (absolute & z-index: 1)
        - div.lev3 (relative & z-index: 10)
        - div.lev3
        - div.lev3
        - div.lev3
        - div.lev3
        - div.lev3
        - div.lev3
        - div.lev3
        - div.lev3
        - div.lev3
        - div.lev3
    - div.lev2
Enter fullscreen mode Exit fullscreen mode

The reason the third to sixth div.lev3 appears under the second div.lev2:

  • All div.lev3 are contained by div#container2
  • div#container2 is contained by the first div.lev2
  • Transitively div.lev3 are contained by the first div.lev2
  • Due to HTML source ordering the second div.lev2's stacking context stacks in front of the first div.lev2 which means it stacks on top of anything that it contains, including those div.lev3s

So the z-index: 10 on the div.lev3 is entirely futile.

However throwing this at the end of style:

   div.lev2:nth-of-type(2) {
     z-index: -1;
   }
Enter fullscreen mode Exit fullscreen mode

has the desired effect as it pulls the second div.lev2 behind the first.

Alternately

  div.lev2:first-of-type {
    z-index: 1;
  }
Enter fullscreen mode Exit fullscreen mode

works as well as it stacks the first div.lev2 in front the second one.

One thing that's confusing about this example is that div.lev1s don't have separate stacking contexts while the div.lev2s do (due to opacity: 0.9). Both div.lev1 and div#container1 exist in the #document stacking context but div#container1's absolute & z-index: 1 creates a stacking context that stacks in front of non-positioned elements (div.lev1).

When each div.lev1 creates it's own stacking context

   div.lev1 {
      isolation: isolate;
   }
Enter fullscreen mode Exit fullscreen mode

The problem happens all over again.

   div.lev1:first-of-type {
      z-index: 1;
   }
Enter fullscreen mode Exit fullscreen mode

fixes it (but only because position: relative is already present).

Collapse
jfbrennan profile image
Jordan Brennan

Thanks for the reply, but tl;dr.

Timing, instead of layering, works for me as a mental model because that’s what the browser does - it decides when to paint an element. It would be easier for me to think of z-index as simple layers if the rules were such.

Collapse
curiousdev profile image
CuriousDev

Thanks, it makes sense, if you think, that "circle" is part of "main", which is below "header". You could also change the "main z-index", at least it depends on what you want to do.

Collapse
theeasydev profile image
Uriel Bitton

Of course it works all the time, you just need to know how to use it and it will always work.
It doesn't work when we dont know how to use it!

Collapse
snelson1 profile image
Sophia Nelson

Nice article

🌚 Browsing with dark mode makes you a better developer by a factor of exactly 40.

It's a scientific fact.