Adam Spindler
Adam Spindler

Reputation: 394

Child with opacity cannot appear below parent's sibling with z-index

I am trying to understand why applying an opacity other than 1 to an element's child makes it always stack on top of the element's siblings.

.element {
  width: 500px;
  height: 100px;
  background: orangered;
}

.child {
  height: 150px;
  width: 100px;
  background: grey;
  
  opacity: 0.5;
  z-index: 1;
}

.sibling {
  width: 400px;
  height: 100px;
  background: skyblue;
  
  z-index: 2;
}
<div class="element">
  <div class="child"></div>
</div>
<div class="sibling"></div>

Removing the opacity property (or setting it to 1) on the .child makes it stack behind the .sibling as expected, but for some reason an opacity value other than that changes its stacking order. It makes me think that .element is somehow forming a stacking context, but as far as I can tell it doesn't meet any of the criteria on MDN's website.

The CSS standard doesn't have anything to say about this from what I understand:

Since an element with opacity less than 1 is composited from a single offscreen image, content outside of it cannot be layered in z-order between pieces of content inside of it. For the same reason, implementations must create a new stacking context for any element with opacity less than 1. If an element with opacity less than 1 is not positioned, then it is painted on the same layer, within its parent stacking context, as positioned elements with stack level 0. If an element with opacity less than 1 is positioned, the ‘z-index’ property applies as described in [CSS21], except that if the used value is ‘auto’ then the element behaves exactly as if it were ‘0’. See section 9.9 and Appendix E of [CSS21] for more information on stacking contexts. The rules in this paragraph do not apply to SVG elements, since SVG has its own rendering model ([SVG11], Chapter 3).

I think all its saying is that if you set opacity to something other than 1, then the element will form its own stacking context and everything inside the transparent element will be transparent together. But in my example, .child may create its own stacking context for elements laid out inside of it, but the element itself would still be in whatever the surrounding stacking context is. This can be tested by setting display: flow-root on .child and removing opacity: 0.5 to force it to create a stacking context, but it will stack behind .sibling as I would normally expect.

So what's going on here?

Upvotes: 4

Views: 361

Answers (2)

Temani Afif
Temani Afif

Reputation: 273389

To start with, the z-index you are applying are useless since no element is positonned. Your code can be simplified like below:

.element {
  width: 500px;
  height: 100px;
  background: orangered;
}

.child {
  height: 150px;
  width: 100px;
  background: grey;
  
  opacity: 0.5;
}

.sibling {
  width: 400px;
  height: 100px;
  background: skyblue;
}
<div class="element">
  <div class="child"></div>
</div>
<div class="sibling"></div>

Then it's not about stacking context but painting order. What you said about stacking context is correct but is irrelevant here since you have no element inside .child.

From your quote you will read:

If an element with opacity less than 1 is not positioned, then it is painted on the same layer, within its parent stacking context, as positioned elements with stack level 0.

Then if you check the painting order

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

From the below, we can understand than an element with opacity less than 1 will be painted like positionned elements and those element are painted at the step (8). Your other elements have nothing special and they didn't create a stacking context so they will be painted before:

  1. For all its in-flow, non-positioned, block-level descendants in tree order:

You will have the same result if you replace opacity with position:relative for example

.element {
  width: 500px;
  height: 100px;
  background: orangered;
}

.child {
  height: 150px;
  width: 100px;
  background: grey;
  
  position:relative;
}

.sibling {
  width: 400px;
  height: 100px;
  background: skyblue;
}
<div class="element">
  <div class="child"></div>
</div>
<div class="sibling"></div>

And if you make the sibling element positionned it will also get painted in the step (8) and will be above the .child because we follow the tree order.

.element {
  width: 500px;
  height: 100px;
  background: orangered;
}

.child {
  height: 150px;
  width: 100px;
  background: grey;
  
  opacity:0.5;
}

.sibling {
  width: 400px;
  height: 100px;
  background: skyblue;
  
  position:relative;
}
<div class="element">
  <div class="child"></div>
</div>
<div class="sibling"></div>


To conclude, the creation of stacking context is only relevant when having child elements since this will oblige them to be painted inside that stacking context then we consider the painting order to see when each element will be painted.

Related questions for more detail:

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

Why does position:relative; appear to change the z-index?

Upvotes: 3

Rahul Panwar
Rahul Panwar

Reputation: 100

Although you have your answer but if you don't want to decrease the height of .element you can add one more css property to it which is overflow: hidden then .child will not overflow from .element.

.element {
  width: 500px;
  height: 100px;
  background: orangered;
  overflow: hidden;
}

.child {
  height: 150px;
  width: 100px;
  background: grey;
  
  opacity: 0.5;
  z-index: 1;
}

.sibling {
  width: 400px;
  height: 100px;
  background: skyblue;
  
  z-index: 2;
}
<div class="element">
  <div class="child"></div>
</div>
<div class="sibling"></div>

Upvotes: 1

Related Questions