DEV Community

Discussion on: Why z-index doesn't work all the time 🤯 ?

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.