Although it may not be immediately obvious, the elements in an HTML document are generated in three dimensions. Besides being aligned on the x- and y- axes, elements can lie on the z-axis, which controls their position in the third dimension. 

The 3 Dimensions

Properties such as margin, float, and the offset properties control how the element sits on the x- and y- axes. The z-index property solely controls how elements are arranged on this z-axis. 

The z-index Property

The z-index property specifies two things -

The property only applies to positioned elements. That is, elements that have a position of relative, absolute, or fixed.

There are three potential values for the z-index property -

Value Description
auto Sets the stacking level to 0 and does not establish a new stacking context
<integer> Sets the stacking level to the integer and establishes a new stacking context
inherit Sets the stacking level to the same as it’s parent element and does not establish a new stacking context
z-index: auto | <integer> | inherit  

Stacking Level

The stacking level is the value on the z-axis that the current element is on. Higher numbers indicate that the element is higher on the stack of elements and closer to the surface of the screen.

Stacking Level

If not specified by the z-index property, the stacking level of an element is established according to it’s arrangement in the document tree. Elements that are declared later in the document have a higher stacking level by default. 

Calculating Stacking Level

In addition to the specified z-index, the stacking level of an element is controlled by a number of factors. Elements are stacked in the following order.

Position Description CSS
1 (bottom) The element forming the stacking context z-index: <integer>
2 Child elements with negative stack levels z-index: <negative integer>
position: relative | absolute | fixed
3 In-flow, non-inline, non-positioned child elements display: /* not inline */
position: static
4 Non-positioned floating child elements float: left | right
position: static
5 In-flow inline, non-positioned child elements display: /* inline */
position: static
6 Child elements with stacking level of 0 z-index: auto | 0
position: relative | absolute | fixed
7 (top) Child elements with positive stack levels z-index: <positive integer>
position: relative | absolute | fixed

Stacking Context

When we specify the stacking level for an element using the z-index property, we are not always specifying the stacking level of the element in relation to every other element on the page. The element’s stacking level is only in relation to its stacking context.

This can lead to some bizarre situations in which an element with a higher number z-index is not always necessarily “above” an element with a lower z-index.

Stacking context can be explained by the following rules.

1. The default stacking context is the root element

The default stacking context for any HTML document is the root  <html> element. Therefore, unless new stacking contexts are created, the stacking level of an element is, by default, in relation to every other element on the page.

2. Establish a new stacking context with the z-index property

We establish a new stacking context by setting the z-index of an element to any integer. This has the effect of, firstly, setting the current element’s stacking level to that integer, and secondly, creating a new stacking context.

The new stacking context applies to any children of that element. Their stacking levels are now only within that stacking context, not the root stacking context.

In the example below, .foo belongs to stacking context 1, whereas .bar belongs to stacking context 2.

Stacking Context

3. Elements cannot be stacked above (or below) the parent element’s stacking level

When the parent element’s stacking level is set, that means that its children also cannot be stacked above or below that level (in relation to the parent element's stacking context). 

In the example below, even though .bar has a higher z-index than .baz, it still shows "below" it. This is because, in stacking context 1, .bar cannot go above or below stacking level 1.

.foo { z-index: 1; }
.bar { z-index: 1000; }
.baz { z-index: 2; }

Stacking Context