The Not So Simple Z-Index
Web development is still a relatively new field for me. I know my basic HTML elements and structure as well as how CSS works but there are aspects of both that I have yet to learn. Z-index, I thought, was not one of them.
On the surface, z-index seems like quite a simple concept; if an element is positioned, you can give it a z-index and control the stacking order of the element relative to those around it. You can easily stack elements on top of each other and then rearrange the order if you want a different stacking order. It turns out that z-index is more than that, despite its deceptive simplicity which leads many not to read the spec and assume they know how it works. Most of the time, they won’t realise that they don’t fully understand how z-index works because it behaves exactly as they expect. There are cases, however, where changing a seemingly unrelated property will alter the stacking order of elements that have been stacked using z-index.
I first stumbled upon this in Philip Walton’s blog where he presents a challenge to his readers to solve a simple puzzle with difficult restrictions. In the challenge, Philip has three div elements with a span in each, all styled in different colours. Philip asks his reader to reorder the divs so that the red is on the bottom, blue is on top and green is in the middle. The challenge is to reorder the divs without altering the HTML, z-index or positioning of any element. The solution? Change the opacity of the red span’s div so that it is less than one.
I let out an audible “huh?” when I read that on Philip’s blog. How could changing the opacity affect the stacking order? It turns out that opacity values less than 1 can create stacking contexts, which can complicate what seems like a simple property. I won’t claim that I fully understand stacking contexts just yet so I’ll let Philip explain them:
Groups of elements with a common parent that move forward or backward together in the stacking order make up what is known as a stacking context. A full understanding of stacking contexts is key to really grasping how z-index and the stacking order work.
Every stacking context has a single HTML element as its root element. When a new stacking context is formed on an element, that stacking context confines all of its child elements to a particular place in the stacking order. That means that if an element is contained in a stacking context at the bottom of the stacking order, there is no way to get it to appear in front of another element in a different stacking context that is higher in the stacking order, even with a z-index of a billion!
New stacking contexts can be formed on an element in one of three ways:
- When an element is the root element of a document (the element)
- When an element has a position value other than static and a z-index value other than auto
- When an element has an opacity value less than 1
The first and second ways to form stacking context make a lot of sense and are generally understood by Web developers (even if they don’t know what they’re called).
The third way (opacity) is almost never mentioned outside of w3c specification documents.
Update: In addition to opacity, several newer CSS properties also create stacking contexts. These include: transforms, filters, css-regions, paged media, and possibly others. As a general rule, it seems that if a CSS property requires rendering in an offscreen context, it must create a new stacking context.
Unfortunately, this complicates stacking elements. If you’re setting a very high z-index on an element and it still isn’t moving to where you want it, look higher up in the DOM for parent elements creating new stacking contexts.
I highly recommend reading Philip’s blog post. I plan on going through it more carefully and getting a handle on stacking contexts before I run into this in the wild.