10

I am trying to understand how z-index works. In order to do that, I have created a simple example which consists of five divs. Each one is a child of the previous, except for the first. My objective is to make the first div, the parent container of all other divs, be shown on top of all of the others, effectively hiding them.

In order to achieve my goal, I have put z-index properties on all the divs and on the parent div I have put an exaggerated value of a 100 to make sure it is higher than all the others but it does not seem to be working.

I have read many different documentations regarding the z-index and have read lots of answers here on Stack Overflow. So far I have tried the following:

  • Adding position properties to all the divs.
  • Adding opacity with a value of 0.99 to the divs I want to hide.
  • Applying different value combinations of the position attribute (eg. relative, fixed, absolute).

Still, I have not had any success in making the parent div appear on top of all the other divs. What am I doing wrong?

I have created a JSFiddle with the example I have just described: https://jsfiddle.net/y8jfdz7w/15/.

.first {
  position: absolute;
  z-index: 100;
  width: 500px;
  height: 500px;
  background-color: grey;
}
.second {
  position: absolute;
  z-index: 2;
  width: 450px;
  height: 450px;
  top: 25px;
  left: 25px;
  background-color: orange;
  opacity: 0.99;
}
.third {
  position: absolute;
  z-index: 3;
  width: 400px;
  height: 400px;
  top: 25px;
  left: 25px;
  background-color: yellow;
  opacity: 0.99;
}
.fourth {
  position: absolute;
  z-index: 20;
  width: 350px;
  height: 350px;
  top: 25px;
  left: 25px;
  background-color: green;
  opacity: 0.99;
}
.fifth {
  position: absolute;
  z-index: 5;
  width: 300px;
  height: 300px;
  top: 25px;
  left: 25px;
  background-color: pink;
  opacity: 0.99;
}
<div class="first">
  <div class="second">
    <div class="third">
      <div class="fourth">
        <div class="fifth">
        </div>
      </div>
    </div>
  </div>
</div>
João Paiva
  • 1,937
  • 3
  • 19
  • 41
  • 2
    See http://stackoverflow.com/questions/1806421/css-parent-element-to-appear-above-child, looks doable with a wrapper and negative z-index on the child element, but why break the natural layering of a child element above its parent? – zebraman Dec 29 '16 at 19:23
  • 6
    "I am trying to understand how z-index works" give up now, no one understands how Z index works – Mike G Dec 29 '16 at 22:20
  • 2
    if all you want to do is hide all of the children, then, simply, hide the first child with `display:none` or similar. Playing with z-index for anything more than making underneath stuff go on top, is fools play...I've spent days.. if you do really want to make things over/under each other - make all containers siblings, and adjust your z-index on each sibling accordingly. – RozzA Dec 30 '16 at 00:23
  • 1
    I always found this article helpful: https://philipwalton.com/articles/what-no-one-told-you-about-z-index/ – AtheistP3ace Jan 08 '17 at 14:15

6 Answers6

9

Practical answers:

Depending on what you want to achieve (visually) you either have to place the elements to be hidden inside a sibling of their current top level parent or you have to rely on visibility to hide them.

Here is the parent sibling solution:

body{
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

.first {
  position: absolute;
  z-index: 1;
  width: 500px;
  height: 500px;
  background-color: grey;
  animation: toggleOpacity 3s infinite;
}
.another-first {
   z-index: 0;
}
.second {
  position: relative;
  z-index: 2;
  width: 450px;
  height: 450px;
  top: 25px;
  left: 25px;
  background-color: orange;
  opacity: 0.99;
}
.third {
  position: absolute;
  z-index: 3;
  width: 400px;
  height: 400px;
  top: 25px;
  left: 25px;
  background-color: yellow;
  opacity: 0.99;
}
.fourth {
  position: absolute;
  z-index: 20;
  width: 350px;
  height: 350px;
  top: 25px;
  left: 25px;
  background-color: green;
  opacity: 0.99;
}
.fifth {
  position: absolute;
  z-index: 5;
  width: 300px;
  height: 300px;
  top: 25px;
  left: 25px;
  background-color: pink;
  opacity: 0.99;
}
@-webkit-keyframes toggleOpacity {
  0%   { -webkit-transform: translateX(-150px); transform: translateX(-150px); }
  50% { -webkit-transform: translateX(150px); transform: translateX(150px); }
  100% {-webkit-transform: translateX(-150px);transform: translateX(-150px);}
}
@-moz-keyframes toggleOpacity {
  0%   { -moz-transform: translateX(-150px); transform: translateX(-150px); }
  50% { -moz-transform: translateX(150px); transform: translateX(150px); }
  100% {-moz-transform: translateX(-150px);transform: translateX(-150px);}
}
@-o-keyframes toggleOpacity {
  0%   { -o-transform: translateX(-150px); transform: translateX(-150px); }
  50% { -o-transform: translateX(150px); transform: translateX(150px); }
  100% {-o-transform: translateX(-150px);transform: translateX(-150px);}
}
@keyframes toggleOpacity {
  0%   { -webkit-transform: translateX(-150px); -moz-transform: translateX(-150px); -o-transform: translateX(-150px); transform: translateX(-150px); }
  50% { -webkit-transform: translateX(150px); -moz-transform: translateX(150px); -o-transform: translateX(150px); transform: translateX(150px); }
  100% {-webkit-transform: translateX(-150px);-moz-transform: translateX(-150px);-o-transform: translateX(-150px);transform: translateX(-150px);}
}
<div class="first"></div>
<div class="another-first">
  <div class="second">
    <div class="third">
      <div class="fourth">
        <div class="fifth">
        </div>
      </div>
    </div>
  </div>
</div>

Using Vals' solution and your original markup, it is possible to bring any of the divs in front by applying z-index:auto to itself and a negative z-index to its immediate child. The limitation here is that it can only be applied to one level. You can't completely reverse the stack with it (if we disable the reset line in JS and click on level 2 and 4, level 4 comes above level 3 but not above level 2). Here's the snippet, click on any div:

window.ziToy = {
  reset: false,
  updateIndexes : function(){
    $('div span').each(function(){
      $(this).text($(this).parent().css('z-index'));
    })
  },
  toggleReset : function () {
    this.reset = !this.reset;
  },
  values:['-1','auto','1']
};

$('div').on('click', function(e){
  e.stopPropagation();

  if (window.ziToy.reset) {
    $('div').css({'z-index':'auto'}); /*reset all divs*/
     $(this).css({'z-index':'auto'});
     $(this).children().css({'z-index':'-1'})
  } else {
    var toy = window.ziToy,
        current = $(this).css('z-index'),        
        next = toy.values.indexOf(current) + 1;
    $(this).css('z-index', toy.values[next % 3])
  };
 
  window.ziToy.updateIndexes();
});

window.ziToy.updateIndexes();
body {
  color: white;
  font-weight: bold;
  font-family: sans-serif;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  padding-top: 30px;
}
@media (max-height: 300px) {
  body{ 
    padding-top: 150px;
  }
}

section {
  width: 0;
  height: 0;
  overflow: visible;
  left: -240px;
  top: -160px;
  position: relative;
  z-index: 1;
}
.toggle {
  position: absolute;
  top:0;
  left: 0;
  padding: 15px;
  color: #999;
  font-weight: 400;
}
div {
  position: absolute;
  height: 150px;
  width: 300px;
  top: 30px;
  left: 30px;
  background-color: grey;
  padding: 5px;
  cursor: pointer;
}

div>div {
  background-color: orange;
}
div>div>div {
  background-color: darkred;
}
div>div>div>div {
  background-color: green;
}
div>div>div>div>div {
  background-color: pink;
}
div>span {float: right;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<section>

  <div><span></span>
    <div><span></span>
      <div><span></span>
        <div><span></span>
          <div><span></span>
          </div>
        </div>
      </div>
    </div>
  </div>
  
</section>
<label class="toggle">
  <input onchange="javascript: window.ziToy.toggleReset()" type="checkbox" />Reset all divs on click
</label>

Updated snippet: now you can disable the divs z-index reset to be able to toggle through values of -1, auto & 1 for each of the <div>s independently. This will probably help in understanding the stacking contexts principle, laid down below in my own words.


Stacking contexts principle:

Each parent with a set position (other than static) and a set z-index (other than auto), creates a stacking context at that particular z-index for all its children. Picture it as an infinity of z-index for its children. That infinity is placed entirely at the z-index of the parent.

Let's consider the reference item A. Regardless of z-index on any children of A, if you set z-index on B (sibling of A) higher than A's z-index, B (and any children of B) will be rendered above A and above all the children of A.

When comparing z-index of children from different parents, the browser will always decide based on z-index of parents, not of children.

If you want to send a child below its parent, set z-index:auto on the parent and a negative z-index on the child.


Important note: When applying transforms (especially 3d) on elements with negative z-index not all browsers behave the same and you might experience bugs and inconsistencies. For example, see this un-aswered question.

Community
  • 1
  • 1
tao
  • 82,996
  • 16
  • 114
  • 150
4

You can hack your way around to make child elements disappear behind the parent element with z-index. Here are a few methods:

But generally speaking, you can't use z-index to position a child element behind the root element of the stacking context.

If you can alter the HTML structure, however, to make the divs siblings, then you're all set:

.first {
  z-index: 5;
  width: 500px;
  height: 500px;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  background-color: grey;
}
.second {
  z-index: 4;
  width: 450px;
  height: 450px;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  background-color: orange;
}
.third {
  z-index: 3;
  width: 400px;
  height: 400px;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  background-color: yellow;
}
.fourth {
  z-index: 2;
  width: 350px;
  height: 350px;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  background-color: green;
}
.fifth {
  z-index: 1;
  width: 300px;
  height: 300px;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  background-color: pink;
}
<div class="first"></div>
<div class="second"></div>
<div class="third"></div>
<div class="fourth"></div>
<div class="fifth"></div>
Community
  • 1
  • 1
Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
  • 1
    By removing the `z-index` of the parent, you are not placing the parent above the child. You are placing the entire document above the child. So technically, it's not possible. If you want to partially cover the child with the parent, you need to make sure the entire document is transparent above the child and make only the parent opaque. That hardly qualifies as "parent" above "child". It's "everything" above "child", really. – tao Dec 29 '16 at 19:54
  • 1
    @AndreiGheorghiu, I'm not clear on what part of my answer you're addressing. Or are you addressing my comment to another answer? – Michael Benjamin Dec 29 '16 at 19:57
  • 1
    I was addressing the part where you are suggesting there are methods to do it. – tao Dec 29 '16 at 20:09
  • 2
    @AndreiGheorghiu, yeah, but I described those methods as hacks. – Michael Benjamin Dec 29 '16 at 20:12
  • 2
    I stand corrected. Vals has demonstrated the item is not sent below the document, but only below its parent. The more I experience with this the more I am convinced it's not a hack, but intended by design, to provide a way to send children behind parents. I updated my answer accordingly and added a snippet that might help others see (visualize) how it works – tao Dec 30 '16 at 12:07
3

The z-index will be calculated relative to the parent, so once you increase the parent's z-index all children will be affected implicitly. There is no way a child can be hidden behind its parent using z-index. z-index mostly affect siblings or HTML elements in different parents.

  • 3
    *There is no way a child can be hidden behind its parent using `z-index`.* That's not exactly true. A negative `z-index` on the child can do it: https://jsfiddle.net/mnux1n3c/ – Michael Benjamin Dec 29 '16 at 21:26
2

I am not sure if this has been already said, there are lots of answers and comments.

You can do it with a z-index: auto on the parent and a negative value on the immediate child

Here on the first level:

body {
  background-color: bisque;  
}

.first {
  position: absolute;
  z-index: auto;
  width: 500px;
  height: 500px;
  background-color: grey;
  animation: togglePosition 3s infinite;
}
.second {
  position: absolute;
  z-index: -1;
  width: 450px;
  height: 450px;
  top: 25px;
  left: 25px;
  background-color: orange;
  opacity: 0.99;
  animation: togglePosition 3s infinite -1.5s;
}
.third {
  position: absolute;
  z-index: 3;
  width: 400px;
  height: 400px;
  top: 25px;
  left: 25px;
  background-color: yellow;
  opacity: 0.99;
}
.fourth {
  position: absolute;
  z-index: 20;
  width: 350px;
  height: 350px;
  top: 25px;
  left: 25px;
  background-color: green;
  opacity: 0.99;
}
.fifth {
  position: absolute;
  z-index: 5;
  width: 300px;
  height: 300px;
  top: 25px;
  left: 25px;
  background-color: pink;
  opacity: 0.99;
}

@keyframes togglePosition {
  0%   {  left: -150px; }
  50%  {  left: 150px; }
  100% {  left: -150px;}
}
<div class="first">
  <div class="second">
    <div class="third">
      <div class="fourth">
        <div class="fifth">
        </div>
      </div>
    </div>
  </div>
</div>

And here on the second level. Here, the opacity needs to be removed. Just to demonstrate that it's not all the document that is above the child, just the relevant parts (if I understand the comment ok)

.first {
  position: absolute;
  z-index: 100;
  width: 500px;
  height: 400px;
  background-color: grey;
}
.second {
  position: absolute;
  z-index: auto;
  width: 450px;
  height: 300px;
  top: 80px;
  left: 25px;
  background-color: orange;
}
.third {
  position: absolute;
  z-index: -1;
  width: 400px;
  height: 400px;
  top: -50px;
  left: 25px;
  background-color: yellow;
  opacity: 0.99;
}
.fourth {
  position: absolute;
  z-index: 20;
  width: 350px;
  height: 350px;
  top: 25px;
  left: 25px;
  background-color: green;
  opacity: 0.99;
}
.fifth {
  position: absolute;
  z-index: 5;
  width: 300px;
  height: 300px;
  top: 25px;
  left: 25px;
  background-color: pink;
  opacity: 0.99;
}
<div class="first">
  <div class="second">
    <div class="third">
      <div class="fourth">
        <div class="fifth">
        </div>
      </div>
    </div>
  </div>
</div>
vals
  • 61,425
  • 11
  • 89
  • 138
  • 1
    Vals, does this send the children under the whole document or only under the parent? Do you think the animation I made in my solution would be possible using the existing markup, (presuming `body` is opaque, of course)? – tao Dec 29 '16 at 21:59
  • 1
    @AndreiGheorghiu See my second snippet (I have edited it). Third element is not only above document, but also above first element. (And of course 4th and so on. About your animation, I am not sure about it, I will try when I have the time. – vals Dec 30 '16 at 08:18
  • 1
    @AndreiGheorghiu I have set a **similar** animation on the first snippet. Yes, of course, transforms ruin the layout, but I think this doesn't mean the layout is not usable. I have also set a color on body, to further show that the elements are above it. – vals Dec 30 '16 at 11:13
  • 1
    You're right, I just made a fiddle demonstrating your solution: https://jsfiddle.net/websiter/5aj3wyma/ . Click on any of the divs :) – tao Dec 30 '16 at 11:19
  • 1
    Unfortunately, that's the limit. This can be done only at one level at a time, you can't do it twice in the same stack. You can't reverse the normal order, because elements can't have `z-index:auto` and negative `z-index` at the same time. Without resetting, if I do the trick on 2nd and 4th (in this order), the 4th comes above 3rd but not above 2nd. – tao Dec 30 '16 at 11:32
1

The child elements always inherit the z-index from its parents as mentioned by another answer!

However, there is a workaround.. set a negative z-index for the child elements, and remove the one set on the parent element to achieve your goal.

Hope this helps!

Tim
  • 1,326
  • 1
  • 15
  • 27
0

Parent elements z-index have an effect.

As seen in another post, this script, listing all parents elements z-index, is very useful to debug.

 var el = document.querySelector('your elt'); 
  do {
    var styles = window.getComputedStyle(el);
    console.log(styles.zIndex, el);
  } while(el.parentElement && (el = el.parentElement));
Matoeil
  • 6,851
  • 11
  • 54
  • 77