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:
- “simple elements”
- “positioned elements”
- “floating elements”
- “z-index alone”
- “z-index vs position”
- “z-index vs various positions”
- “equal z-index values”
- “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
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:
position: absolute
position: fixed
position: inherit
position: initial
position: relative
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
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):
- The background and borders of the
<html>
element - Block elements in the normal flow
- 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
#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 theposition
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):
- The background and borders of the
<html>
element - Block elements in the normal flow
- Block elements having a
float
property - Inline elements in the normal flow
- 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
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
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, aposition
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
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:
<#four.relative>
<#three.absolute>
<#two.absolute>
<#one.relative>
So, whats new?
- Elements positioned with
position: relative
andposition: 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
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:
<#three.zIndex1>
<#four.zIndex1>
<#one.zIndex2>
<#two.zIndex2>
Good, this is some good news
- The stacking of elements sharing the same
position
andz-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:
- The background and borders of the
<html>
element - Block elements having a non-default
position
property and a negativez-index
value - Block elements in the normal flow
- Block elements having a
float
property - Inline elements in the normal flow
- Block elements having a non-default
position
property - Block elements having a non-default
position
property and az-index
of0
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
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
- The background and borders of the
<html>
element - Block elements having a non-default
position
oropacity
property and a negativez-index
value - Block elements in the normal flow
- Block elements having a
float
property - Inline elements in the normal flow
- Block elements having a non-default
position
oropacity
property - Block elements having a non-default
position
oropacity
property and az-index
of0
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!