.demo-wrapper

header

.main

In this example we have:

Despite the fact the nav has z-index: 1, it appears underneath the .main div element that appears later in the markup.

So, thanks to "What no one told you about z-index", we know the following:

Ways to create a stacking context

  1. Root html element
  2. Elements with position other than static and z-index other than auto
  3. Elements with opacity other than 1

Ordering of elements within a stacking context

  1. Stacking context root element
  2. Positioned elements (and children) with negative z-index values (ties broken by order of appearance in the DOM)
  3. Non-positioned elements (in the order they appear in the DOM)
  4. Positioned elements (and children) with z-index: auto (in the order they appear in the DOM)
  5. Positioned elements (and children) with positive z-index values (ties broken by order of appearance in DOM)

The fact that no value of z-index on the nav element is sufficient to make it appear in front of the .main element indicates that we must have two stacking contexts at play here.

If we remove the erroneous position: relative on the .main element, then our z-index takes effect.

What could be creating a stacking context other than the root html element, given that no elements in our example have opacity other than 1, and no elements have non-static position and non-auto z-index?

Here header and .main are peers, and both are positioned. Neither has a z-index. Clearly, however, header forms a stacking context, because the z-index of elements inside it are unable to ever appear in front of anything outside the header.

.main ends up trumping anything in header, because .main appears after it in the DOM.

So, the stacking context is formed inside header because it is positioned, and it contains a descendent with a z-index. "Creation rule #2" above, "[e]lements with position other than static and z-index other than auto", makes it sound like the position and z-index must be on the same element, but this example makes it clear that that is not the case. Perhaps it is true that the non-auto z-index on the descendant forces the parent to have an implicit z-index of 0 (although the DOM inspector seems to refute this, showing an inherited value of auto).

An alternative explanation is that the two peers that are positioned will always appear relative to one another in the DOM due to "ordering rule #4". This in turn makes z-index pretty useless inside positioned elements, at least with respect to other positioned elements. Note that even setting a smaller z-index on the trumping value is not enough to fix the problem.

I think to fully lay this to rest I'll need to get a university degree in the spec.