shopping24 tech blog

s is for shopping

March 02, 2014 / by Kim Hogeling / Web developer / @kimhogeling

Stacking context, a deep dive

Today stacked context, HTML elements flying all over the websites, can be found everywhere. I love it, but to get it done, I always catch myself wrestling with the position and z-index properties untill it magically works.. somehow..

Although these properties look so simple, why is the stacking context acting so misteriously in some cases? About time I take a closer look at the behaviour of stacked context and document it for reference.

Please join me on my little adventure!

“Ain’t nobody got time for details.. Just take me to the final list!”

About this article

As the title says, this article is about context stacking. In other words: We will examine the behaviour of browsers at rendering and painting elements that overlap each other under several conditions.

I will create some tests and summerize my discoveries. I find it important, to keep researches like these as simple as possible but as we dive deeper into the topic they might become a little complex.

Our goal is to create a list of the stacking order.

The tests

Because all work and no play makes you a dull boy, I’ve added the tests in HTML form for you instead of using images, so you can simply inspect the source code and play around. Yaaay!!

If you are reading this article in a modern browser, you should see shadows and transparency in the result, which should help emphasizing the stacking.

The magnificent list of tests:

  1. “simple elements”
  2. “positioned elements”
  3. “floating elements”
  4. “z-index alone”
  5. “z-index vs position”
  6. “z-index vs various positions”
  7. “equal z-index values”
  8. “Opacity”

I haven’t tested the tests in all of them older browsers, but they might look less pretty, but should work just fine.

“But I have IE6.”.. seriously?? Its 13 years behind! Turn off your P5 and go yell at children to get off your lawn!!

Test 1 - simple elements

To begin, we should observe the default behaviour.

There are four simple <div> elements and with the help of some negativ margin, they lay over each other.

Test 1 - simple elements - HTML

<div id="one">
    #one
</div>
<div id="two">
    #two
</div>
<div id="three">
    #three
</div>
<div id="four">
    #four
</div>

Test 1 - simple elements - CSS

#one, #two, #three, #four {
    padding: 20px;
    height: 30px;
    width: 50px;
}

#one {
    background: yellow;
}

#two {
    margin: -30px 0 0 10px;
    background: cyan;
}

#three {
    margin: -100px 0 0 80px;
    background: purple;
}

#four {
    margin: -20px 0 0 90px;
    background: red;
}

Test 1 - simple elements - Result

#one
#two
#three
#four

Test 1 - simple elements - Conclusion

The stacking simply matches the order of the markup: <#one> is overlapped by <#two>, which is overlapped by <#three>, which is overlapped is by <#four>.

As our first test shows, simple elements, not having any funny properties, behave logical and as expected.

Test 2 - positioned elements

Our first property, position, accepts six values:

  1. position: absolute
  2. position: fixed
  3. position: inherit
  4. position: initial
  5. position: relative
  6. position: static

I will concentrate on position: absolute and position: relative, because adding the tests in HTML form makes it hard to use the other values. But luckely its all we need to show the connection between the position property and stacking context.

Our second test is just like the first, extended with the position property. Lets see how stacking context reactes to this.

Test 2 - positioned elements - HTML

<div id="one" class="relative">
    #one.relative
</div>
<div id="two" class="absolute">
    #two.absolute
</div>
<div id="three">
    #three
</div>
<div id="four" class="relative">
    #four.relative
</div>

Test 2 - positioned elements - CSS

#one, #two, #three, #four {
    padding: 20px;
    height: 30px;
    width: 90px;
}


.relative {
    position: relative;
}

.absolute {
    position: absolute;
}


#one {
    background: yellow;
}

#two {
    top: 50px;
    left: 20px;
    background: cyan;
}

#three {
    margin: -60px 0 0 100px;
    background: purple;
}

#four {
    margin: -20px 0 0 110px;
    background: red;
}

Test 2 - positioned elements - Result

#one.relative
#two.absolute
#three
#four.relative

Test 2 - positioned elements - Conclusion

Looking at the result, we can see that <#two.absolute> is overlapping <#one.relative>, which is overlapped by <#four.relative>. This was expected, because this behaviour matches the order of the markup.

But what is going on with <#three>?! Why isn’t it drawn in front of <#one.relative> and <#two.absolute>? According to the markup it should, but instead it’s drawn way at the back!

What turns out is, that using the position property has taken <#one.relative>, <#two.absolute> and <#four.relative> out of the normal flow. Thats why <#three> is drawn first and <#one.relative>, <#two.absolute> and <#four.relative> follow to be drawn in front of <#three>.

Hey! We’ve already gathered two facts about the stacking context:

  • Elements without the position property always lie below elements with it.
  • This behaviour can contradicts the order of the markup.

The first stacking list

Our first and short list for the stacking context (back to front):

  1. The background and borders of the <html> element
  2. Block elements in the normal flow
  3. Block elements having a non-default position property

Test 3 - floating elements

Well, wasn’t that test exciting? Not really? Then let us proceed quickly and add some spicy floating elements!

To keep it simple, we’ll simply enhance test 2 by adding two new <div> elements. One at the beginning of the markup and one of after the positioned elements. Both will float either left or right.

Test 3 - floating elements - HTML

<div id="one" class="floatLeft">
    #one.floatLeft
</div>
<div id="two" class="absolute">
    #two.absolute
</div>
<div id="three" class="relative">
    #three.relative
</div>
<div id="four" class="floatRight">
    #four.floatRight
</div>
<div id="five" class="normal">
    <p style="border:1px dotted #0aa">
        #five.normal This is some extra text to show the breaking
    </p>
</div>

Test 3 - floating elements - CSS

#one, #two, #three, #four, #five {
    padding: 15px 10px;
    height: 30px;
    width: 90px;
}

#one.floatLeft {
    float: left;
    margin: 0 0 0 20px;
    height: 30px;
    width: 90px;
    background: yellow;
}

#two.absolute {
    position: absolute;
    top: 20px;
    left: 130px;
    height: 150px;
    width: 80px;
    background: cyan;
}

#three.relative {
    position: relative;
    margin: 40px 0 0;
    height: 30px;
    width: 110px;
    background: purple;
}

#four.floatRight {
    float: right;
    margin: -10px 80px 0 0;
    height: 50px;
    width: 90px;
    background: red;
}

#five.normal {
    margin: -5px 0 0 10px;
    height: 70px;
    width: 120px;
    background: green;
}

Test 3 - floating elements - Result

#one.floatLeft
#two.absolute
#three.relative
#four.floatRight

#five.normal - And some extra text to show the breaking

The result shows <#one.floatLeft> and <#four.floatRight> both lie in front of <#five.normal> and behind the positioned divs <#two.absolute> and <#three.relative>. We can also see something weird. Altough the background and shadow of <#three.relative> remain untouched, its content (marked with the dashed border) is pushed down by <#one.floatLeft>. The same effect is repeated for the content of <#five.normal> (marked with the dotted border), where the border is unaffected, but nevertheless, the words still break, because of the floating <#four.floatRight>.

Test 3 - floating elements - Conclusion

Hurray! We’ve learned a lot this time:

  • Elements with the float property always lie between elements with and without the position property.
  • Floating elements all appear at the exact same level, their order in the markup does not effect this.
  • Backgrounds and borders of blocks and their inline content lie on different levels, which can be made visible with floating elements.
  • Although block elements in the normal flow lie behind floating elements, its content -the inline elements- lie in front of the floating elements.

The second stacking list

Now we can enhance our list (back to front):

  1. The background and borders of the <html> element
  2. Block elements in the normal flow
  3. Block elements having a float property
  4. Inline elements in the normal flow
  5. Block elements having a non-default position property

Test 4 - z-index alone

So far we haven’t used the z-index property yet. About time we will!

But first..

The z-index today

Before we get started, I’d like to mention how the z-index property is used today.

Today most web pages are not simply two-dimensional (flat) anymore. Instead the layered presentation is brought into action as much as possible. That means that elements are hovering over other elements and stick on the screen while you scroll. How beauteous!!

To help us organizing the order of overlapping elements the z-index property was introduced in CSS 2.1.

Its basic support is included all current browsers:

  • Chrome 1.0
  • Firefox 1.0 (Gecko 1.7)
  • Internet Explorer 4.0
  • Opera 4.0
  • Safari 1.0

You can read more about the specification of the z-index property at w3.org

This is great and all, so what’s all the fuzz about? Well.. Most of us developers never really have torn the z-index property apart. As a consequence you find the most impossible values in the internetz.

Having that said, lets begin!

I start with two normal <div> elements without any extra properties. Just the z-index property and to make the <div> elements overlap, I set some negativ margin.

Test 4 - z-index alone - HTML

<div id="one" class="zIndex2">
    #one.zIndex2
</div>
<div id="two" class="zIndex1">
    #two.zIndex1
</div>

Test 4 - z-index alone - CSS

#one, #two {
    padding: 20px;
    height: 30px;
    width: 90px;
}

zIndex1 {
    z-index: 1;
}

zIndex2 {
    z-index: 2;
}

#one {
    margin: 0;
    background: yellow;
}

#two {
    margin: -20px 0 0 20px;
    background: cyan;
}

Test 4 - z-index alone - Result

#one.zIndex2
#two.zIndex1

Test 4 - z-index alone - Conclusion

Hey, that’s weird! Why is <#one.zIndex1> in front of <#two.zIndex2>? That doesn’t match the z-index values.

At least we learned something:

  • The z-index property doesn’t affect the context stack if it is used all alone.

Test 5 - z-index vs position

Let’s add the position property and see what happens.

To keep it simple, I start with just position: relative.

Test 5 - z-index vs position - HTML

<div id="one" class="zIndex2 relative">
    #one.zIndex2.relative
</div>
<div id="two" class="zIndex1 relative">
    #two.zIndex1.relative
</div>

Test 5 - z-index vs position - CSS

#one, #two {
    padding: 20px;
    height: 30px;
    width: 130px;
}

.relative {
    position: relative;
}

.zIndex1 {
    z-index: 1;
}

.zIndex2 {
    z-index: 2;
}

#one {
    background: yellow;
}

#two {
    margin: -20px 0 0 20px;
    background: cyan;
}

Test 5 - z-index vs position - Result

#one.zIndex2.relative
#two.zIndex1.relative

Test 5 - z-index vs position - Conclusion

Yaaay! That’s exactly what we wanted! <#one.zIndex2> having the higher z-index property is now in front of <#two.zIndex1>.

Once again, we’ve learned something:

  • To have the z-index take effect, a position is needed.

Test 6 - z-index vs various positions

Now I’d like to mix position: relative and position: absolute.

Test 6 - z-index vs various positions - HTML

<div id="one" class="relative">
    #one.relative
</div>
<div id="two" class="absolute">
    #two.absolute
</div>
<div id="three" class="absolute">
    #three.absolute
</div>
<div id="four" class="relative">
    #four.relative
</div>

Test 6 - z-index vs various positions - CSS

#one, #two, #three, #four {
    padding: 30px 20px;
    height: 30px;
    width: 100px
}

.relative {
    position: relative
}

.absolute {
    position: absolute
}

#one.relative {
    z-index: 4;
    background: yellow;
}

#two.absolute {
    z-index: 3;
    top: 20px;
    left: 135px;
    background: cyan;
}

#three.absolute {
    z-index: 2;
    top: 90px;
    left: 155px;
    background: purple;
}

#four.relative {
    z-index: 1;
    margin: -20px 0 0 10px;
    background: green;
}

Test 6 - z-index vs various positions - Result

#one.relative
#two.absolute
#three.absolute
#four.relative

Test 6 - z-index vs various positions - Conclusion

The result doesn’t look too complicated.

The stacking order from back to front is as followed:

  1. <#four.relative>
  2. <#three.absolute>
  3. <#two.absolute>
  4. <#one.relative>

So, whats new?

  • Elements positioned with position: relative and position: absolute share the same stacking priority.

The stacking list, is not enhanced for now.

Test 7 - equal z-index values

But what happens with positioned elements sharing the same z-index value?

Let’s try it out!

Test 7 - equal z-index values - HTML

<div id="one" class="zIndex2">#one.zIndex2</div>
<div id="two" class="zIndex2">#two.zIndex2</div>
<div id="three" class="zIndex1">#three.zIndex1</div>
<div id="four" class="zIndex1">#four.zIndex1</div>

Test 7 - equal z-index values - CSS

#one, #two, #three, #four {
    position: relative;
    padding: 30px 20px;
    height: 30px;
    width: 100px
}

.zIndex1 {
    z-index: 1
}

.zIndex2 {
    z-index: 2
}

#one.zIndex2 {
    background: yellow;
}

#two.zIndex2 {
    margin: -20px 0 0 10px;
    background: cyan;
}

#three.zIndex1 {
    margin: -150px 0 0 130px;
    background: purple;
}

#four.zIndex1 {
    margin: -20px 0 0 140px;
    background: green;
}

Test 7 - equal z-index values - Result

#one.zIndex2
#two.zIndex2
#three.zIndex1
#four.zIndex1

Test 7 - equal z-index values - Conclusion

As we can see, both <div> elements with the higher z-index values lie in front of those with the lower z-index values.

The order from back to front is like this:

  1. <#three.zIndex1>
  2. <#four.zIndex1>
  3. <#one.zIndex2>
  4. <#two.zIndex2>

Good, this is some good news

  • The stacking of elements sharing the same position and z-index matches the order in the markup.

OK, now that we’ve tested the behaviour of the stacking using the z-index property, we can finally enhance our stacking list:

  1. The background and borders of the <html> element
  2. Block elements having a non-default position property and a negative z-index value
  3. Block elements in the normal flow
  4. Block elements having a float property
  5. Inline elements in the normal flow
  6. Block elements having a non-default position property
  7. Block elements having a non-default position property and a z-index of 0 or greater

Test 8 - Opacity

Now this is where it starts go get really crazy.

The last thing you would expect the have an influence on the stacking context would be the opacity property. “The last thing” is exaggerated, but it definitely not the first..

Test 8 - Opacity - HTML

<div>#one.opacity</div>
<div>#two</div>

<div>#one.opacity</div>
<div>#two.relative</div>

Test 8 - Opacity - CSS

.opacity {
    opacity: 0.9
}

.relative {
    position: relative
}

#one, #two, #three, #four {
    padding: 20px;
    height: 30px;
    width: 100px
}

#one {
    background: yellow
}

#two {
    margin: -20px 0 0 10px;
    background: cyan
}

#three {
    margin: -100px 0 0 100px;
    background: purple
}

#four {
    margin: -20px 0 0 110px;
    background: red
}

Test 8 - Opacity - Result

#one.opacity
#two
#one.opacity
#two.relative
#one.relative
#two.relative
#three.relative.opacity
#four.relative

Test 8 - Opacity - Conclusion

As the results show, the opacity property takes an element out of the normal flow and puts it on the same flow as positioned elements.

Now, we can complete the list!

The final stacking list

  1. The background and borders of the <html> element
  2. Block elements having a non-default position or opacity property and a negative z-index value
  3. Block elements in the normal flow
  4. Block elements having a float property
  5. Inline elements in the normal flow
  6. Block elements having a non-default position or opacity property
  7. Block elements having a non-default position or opacity property and a z-index of 0 or greater

Final words

This was an exciting experiment and I hope this article will be of some help for our web community.

If you read this article and found any mistakes or if you have suggestions or comments, please feel free to contact me via email, twitter, google+ or any way you like! I would appreciate it!