Ordinarily, there’s little use for the CSS z-index property. If you are unsure exactly what it is, the W3C spec describes it thus:

In CSS 2.1, each box has a position in three dimensions. In addition to their horizontal and vertical positions, boxes lie along a “z-axis” and are formatted one on top of the other. Z-axis positions are particularly relevant when boxes overlap visually. This section discusses how boxes may be positioned along the z-axis.

For the most part, there isn’t much use for the property as things take care of themselves:

Boxes with the same stack level in a stacking context are stacked back-to-front according to document tree order.

This basically means that elements later in the HTML render automatically higher than those below. The crucial thing to remember with z-index is that it ONLY works on elements that have their ‘position’ value set to something other than ‘static’ (which is the default). So your element needs to be position: absolute;, position: relative; or position: fixed; for z-index to take affect.

Z-index and stacking contexts

It’s not something you are likely to come across everyday but if you are using experimental CSS properties in your design (for example CSS 3D transforms), I think it’s worth re-visiting how z-index stacking ‘contexts’ can trip you up.

If you are designing something with Drop-downs, modals and collapsing sections and can’t understand why elements appear in the order they do. It’s perhaps because of the ‘stacking contexts’ phenomenon.

I hit this exact problem when I had a DOM element near the root of the HTML node that I couldn’t get to appear behind something much further down the DOM tree. Here’s a demonstration:

See the Pen FiKvs by Ben Frain (@benfrain) on CodePen

When I first hit this, a quick search turned up Philip Walton’s post on the topic.

To crudely summarise Philips post (apologies for the bastadisation: you should go read it, like now) certain CSS properties can establish a new stacking context for elements. There is deeper (but very good) explanations of the stacking context phenomonen at MDN.

Understanding what stacking contexts were certainly set me off in the right direction to solve my issues. I won’t re-iterate Philips post except to say that what is important from my POV is the following description in the W3C spec:

In future levels of CSS, other properties may introduce stacking contexts …

The spec goes on to give the example of opacity (which is what Philip uses as an example in his post) however, in my case, the offending item was a CSS transform.

Interestingly, it doesn’t need to be on the element in question either (e.g. the one you can’t get to appear where you want). For example, a transform on the root element will, due to the nature of z-index stacking contexts, affect the z-index of items within it.

Curve balls

There are curve balls. Safari in iOS 7 and iOS 6 create stacking contexts differently if you are using -webkit-overflow-scrolling on an element. Take a look at the following and compare on iOS 6 and iOS 7 if you can:

See the Pen FAJmh by Ben Frain (@benfrain) on CodePen

To summarise what you would see: in iOS 7 and latest desktop Chrome and Safari, clicking the button adds a 3D Transform to the root of the pink element. As the aqua coloured element is a node off that it is sent to the back of the stacking order (as the context has changed).

However, iOS 6 does things differently. Regardless of the 3D transform, in the example above, it still renders the grey section above the others. The reason for this in iOS 6 is the -webkit-overflow-scrolling: touch; property. This makes iOS 6 very unhappy if it’s applied to an element and all its decendants. Thankfully we can get around this particular issue fairly easily (I say easily, maybe for you but it took me about an hour and a half to figure out):

A couple of solutions: you can either work around the issue by limiting the -webkit-overflow-scrolling: touch; property to the exact element/level you want to have the touch scroll. For example: .parent-to-have-overflow-not-descendents > * -webkit-overflow-scrolling: touch; instead of .parent-to-have-overflow-not-descendents * -webkit-overflow-scrolling: touch;. Alternatively, if the option exists, you can toggle the property to -webkit-overflow-scrolling: auto;

In the following example I have wrapped the scrolling element in another div to achieve this. Now you should see consistency across iOS 6, iOS 7, Chrome and Safari 6.05. Note, it’s also important that the final element is a block level element and not an inline element.

See the Pen Baunt by Ben Frain (@benfrain) on CodePen

Conclusion

So, what’s the take away here? Simple. If you are having problems with z-index’ing items and you have the position property applied and a correct z-index value applied, ensure you don’t have a CSS property such as transform on the root element (or -webkit-overflow-scrolling: touch; on iOS 6). It’s more than likely that this is creating a new stacking context for the enclosed elements.