Why can't an element with a z-index value cover its child?
Asked Answered
D

4

52

Today after hours of debugging, I learned in the hard way that:

A parent element is never able to cover (stack on top of) its child element if the parent has a z-index of any value, no matter how you change the child's CSS

How can I understand this behavior by logic? Is it in the specs?

.container {
  width: 600px;
  height: 600px;
  background-color: salmon;
  position: relative;
  z-index: 99;
  padding-top: 10px;
}

h1 {
  background-color: pink;
  position: relative;
  z-index: -1;
  font-family: monospace;
}
<div class="container">
  <h1>1. I can never be covered by parent if my z-index is positive.</h1>
  <h1>2. Even when my z-index is nagative, I still can never be covered if my parent has any z-index at all.</h1>
</div>

Negative z-index child above parent element

Dorena answered 27/2, 2019 at 4:9 Comment(8)
check this out, would be an interesting read...Addressograph
developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/…Addressograph
philipwalton.com/articles/what-no-one-told-you-about-z-indexFlop
@Dorena Did you try removing z-index property from the parent while keeping z-index:-1 on the child? I tried it and than the parent covered the child. Do you know the explanation behind it?Crassus
@HarisGhauri the accepted answer has the explanation you need ;)Camilia
Just to test what I've learned. Remove either z-index or position: relative; from the parent will all cause the parent to lie on top of its children, because which will cause the parent to not establish a local stacking context anymore, which makes z-index: -1 of the child not to be local to the parent's stacking context, but will run in this painting order: (the child is painted in step 3, the parent is painted in step 4 or 8). This is what I've learned from the accepted answer, I think this's a pretty tricky topic so I'm not sure if this is right, am I right? @TemaniAfifFlotage
@LoiNguyenHuynh yes it's right. The main trick is to avoid having the child element trapped inside a stacking context created by its parent element. Both (parent and child) will then belong to the same stacking context (an upper dom element) and they will behave like sibling elements (you can have any painting order between both)Camilia
@TemaniAfif I've left a comment on the question you closed a moment ago. Could you please advise? As my question isn't answered by this one as its a completely different question.Rhine
C
80

There are two important things you need to know: the painting order and the stacking context. If you refer to the specification, you can find how and when elements are painted.

  1. Stacking contexts formed by positioned descendants with negative z-indices (excluding 0) in z-index order (most negative first) then tree order.

  1. All positioned, opacity or transform descendants, in tree order that fall into the following categories:

    1. All positioned descendants with 'z-index: auto' or 'z-index: 0', in tree order.

  1. Stacking contexts formed by positioned descendants with z-indices greater than or equal to 1 in z-index order (smallest first) then tree order.

It's clear from this that we first paint elements with negative z-index at step (3), then the one with z-index equal to 0 at step (8), and finally the ones with positive z-index at step (9), which is logical. We can also read in another part of the specification:

Each box belongs to one stacking context. Each box in a given stacking context has an integer stack level, which is its position on the z-axis relative to other boxes in the same stacking context. Boxes with greater stack levels are always formatted in front of boxes with lower stack levels. Boxes may have negative stack levels. Boxes with the same stack level in a stacking context are stacked bottom-to-top according to document tree order.


To understand when each element will be painted you need to know its stacking context and its stack level inside this stacking context (defined by z-index). You also need to know whether that element establishes a stacking context. This is the tricky part, because setting z-index will do this:

For a positioned box, the z-index property specifies:

  1. The stack level of the box in the current stacking context.
  2. Whether the box establishes a stacking context

Values have the following meanings:

<integer>

This integer is the stack level of the generated box in the current stacking context. The box also establishes a new stacking context.

auto

The stack level of the generated box in the current stacking context is 0. The box does not establish a new stacking context unless it is the root element.


Now we have all the information to better understand each case. If the parent element has a z-index value of something other than auto, then it will create a stacking context, thus the child element will be painted inside whatever their z-index is (negative or positive). The z-index of the child element will simply tell us the order of painting inside the parent element (this covers your second point).

Now, if only the child element has a positive z-index and we set nothing on the parent element, then considering the painting order, the child will be painted later (in step (9)) and the parent in step (8). The only logical way to paint the parent above is to increase the z-index, but doing this will make us fall into the previous case where the parent will establish a stacking context and the child element will belong to it.

There is no way to have the parent above a child element when setting a positive z-index to the child. Also there is no way to have the parent above the child if we set a z-index to the parent element different from auto (either positive or negative).1

The only case where we can have a child below its parent is to set a negative z-index on the child element and keep the parent at z-index: auto, thus this one will not create a stacking context and following the painting order the child will be painted first.


In addition to z-index, there are other properties that create a stacking context. In case you face an expected stacking order, you need to consider those properties, too, in order to see if there is a stacking context created.


Some important facts that we can conclude from the above:

  1. Stacking contexts can be contained in other stacking contexts, and together create a hierarchy of stacking contexts.
  2. Each stacking context is completely independent of its siblings: only descendant elements are considered when stacking is processed.
  3. Each stacking context is self-contained: after the element's contents are stacked, the whole element is considered in the stacking order of the parent stacking context. ref

1: there is some hacky ways if we consider the use of 3D transformation.

Example with an element going under its parent element even if this one has a z-index specified.

.box {
  position:relative;
  z-index:0;
  height:80px;
  background:blue;
  transform-style: preserve-3d; /* This is important */
}
.box > div {
  margin:0 50px;
  height:100px;
  background:red; 
  z-index:-1; /* this will do nothing */
  transform:translateZ(-1px); /* this will do the magic */
}
<div class="box">
  <div></div>
</div>

Another example where we can place an element between two elements in another stacking context:

.box {
  position: relative;
  transform-style: preserve-3d;
  z-index: 0;
  height: 80px;
  background: blue;
}

.box>div {
  margin: 0 50px;
  height: 100px;
  background: red;
  z-index: 5;
  transform: translateZ(2px);
}

.outside {
  height: 50px;
  background: green;
  margin: -10px 40px;
  transform: translateZ(1px);
}

body {
  transform-style: preserve-3d;
}
<div class="box">
  <div></div>
</div>

<div class="outside"></div>

We can also have some crazy stacking order like below:

.box {
  width: 100px;
  height: 100px;
  position: absolute;
}

body {
  transform-style: preserve-3d;
}
<div class="box" style="top:100px;left:50px;background:red;"></div>
<div class="box" style="top: 50px;left: 115px;background:blue;"></div>
<div class="box" style="top: 101px;left: 170px;background:green;"></div>
<div class="box" style="top: 175px;left: 115px;background:purple;transform: rotateY(-1deg);"></div>

CSS circular stacking context

We should note that using such hack may have some side effect due to the fact that transform-style, perspective and transform will affect position:absolute/fixed element. Related: Why does applying a CSS-Filter on the parent break the child positioning?

Camilia answered 27/2, 2019 at 10:43 Comment(9)
Great, this answer covers all the obscure edges from the articles, other answers I've ever read about this topic until now. I'd not been able to distinguish between the terms stack context, positioned element, opacity forms a stacking context...(then does it become a positioned element or what?), when z-index actually affects, bla bla,v.v.v. Now I know there's stack level, painting order (which is far more complicated than the painting order I've thought) . After reading this answer I feel like I'm mastering stacking context/ painting order/ z-index stuff now.Flotage
Would you please explain the "in tree order"?Delegate
@Delegate ther order of how the html code is written. If a div is writen after another one so it comes after in the tree order. if a div is a child of another element so the parent is the frst in the tree order and so onCamilia
@TemaniAfif but doing this will make us fall into the previous case where the parent will establish a stacking context and the child element will belong to it This raises a question for me. So do elements which belong to an element that has created a stacking context get painted over it? Are these lines exactly related to my question you commented on last night( my time zone:) )? You gave the body a negative z-index and gave the child element a negative z-index then it went behind the parent and at the same time remained clickable. Is that because the body became a parent stacking context?Delegate
In step 3 (of painting order) it says "Stacking contexts formed by positioned descendants with negative z-indices (excluding 0) in z-index order (most negative first) then tree order." If this is the case, then if we give the body a z-index of 0, it should still be painted later than the child div (in my question) which is a descendant of the body, and thus still remain unclickable... What am I getting wrong? Sorry for over-commenting I hope this helps making it less vague. Also here's the code in my question I deleted, just in case it's needed: cdpn.io/e/oNXJvQgDelegate
@Delegate You should not read the rules of the painting order considering all the elements at the same time. If you check the links you will read the following: The painting order for the descendants of an element generating a stacking context (see the z-index property) is .. so all the rules apply inside one stacking context. Without z-index applied to body, this one will not create a stacking context and will be on the same one of your other element and logiclly that one (with negative z-index will be painted before).Camilia
@Delegate when the body is having any value of z-index different from auto it will create a stacking context then it's completely different. You first paint what is inside it (so your previous element will now belong to it) then you paint the body and everything inside it as a whole in the stacking context of the body. If you read my answer you will also find: Each stacking context is self-contained: after the element's contents are stacked, the whole element is considered in the stacking order of the parent stacking context.Camilia
@TemaniAfif Right thanks, but unfortunately my real problem has more layers inside the body and just giving the body a z-index of 0 will not make it work. So do I have to specify a z-index of 0 for all parent layers other than the body as well?Delegate
@Delegate not all of them, simply the closest one. I used it on the body because it was the only elementCamilia
L
3

A good way to think about this is that each parent contains its own stacking context. Sibling elements share a parent's stacking order and may, therefore, overlap each other.

A child element is ALWAYS getting a stacking context based on its parent. Hence the need for a negative z-index value to push the child "behind" its parent (0) stacking context.

The only way to remove an element from its parent's context is using position: fixed since this essentially forces it to use the window for context.

Larissa answered 27/2, 2019 at 4:32 Comment(1)
How do I understand the fact that a parent can never cover its child if the parent has its own z-index, say 99? I edited the question title to be more explicit, but didn't edit question content.Dorena
P
2

The Mozilla documentation does say

The z-index CSS property sets the z-order of a positioned element and its descendants or flex items.

Here's some additional logic from another StackOverflow article relating to children vs descendants.

Phantasmal answered 27/2, 2019 at 4:15 Comment(2)
That's a good read. Here's the relevant part: ...are children of DIV #3, so stacking of those elements is completely resolved within DIV#3.Larissa
Thanks for your answer, but it didn't answer the title of the question, sorry I edited the question title a little bit, but didn't edit the contentDorena
S
1

How can I understand this behavior by logic?

For me it's hard to understand your problem by logic. A parent contains its children. A bowl can be covered by another bowl. But you can't cover the soup with the bowl unless you put the soup out of the bowl.

z-Index sets the order for overlapping elements. A parent can't overlap its child.

ImhO that's perfectly logical.

Savoie answered 27/2, 2019 at 6:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.