The Quirks of z‑index

Hero image for The Quirks of z‑index. Image by Josh Rose.
Hero image for 'The Quirks of z‑index.' Image by Josh Rose.

Zindex is one of those classic examples of easytoforget CSS that nonetheless can frustrate even good developers to no end. It's not uncommon to see elements with zindex declarations like zindex: 9999 !important; on them, even though the layouts in question might not look overly complex.

So, what's up? Why can zindex be such a fiddly and easily misused and misunderstood declaration? It all comes down to a few major quirks that come with the zindex style rule, which start off simple but then become increasingly harder to visualise. Let's see what's going wrong with your styling.


Remember to apply Positioning.

If you are seeing unexpected behaviour with your zindex stacks, the first thing to check is your positioning declarations. It is perhaps a little counterintuitive, but zindex just simply will not work at all unless the element also has position applied, even if it is just position: relative; with no further positioning.

Zindex doesn't apply to elements that are statically positioned (meaning that they've either got no position declaration at all, or have position: static; applied to them). Any element with any other positioning assigned to it, will apply zindex styling as expected (or as far as the other quirks allow). So, first things first: check your positioning. Easy enough, right? We're just getting started.


Context is Key.

Context is where I would say I most commonly see developers struggling with overlaying elements using zindex, and is a far more complex issue. Stacking context is largely affected by your markup and the way elements are grouped and ordered (for now), which makes this a bit more of a pain to resolve.

If you're having some issues making an element appear over other elements, despite applying positioning and sending the zindex declaration to the moon, it's time to check up the DOM tree.

A common example where this kind of issue shows up is modal windows and popups. If the HTML for your modal is outputting into the footer of your site, and your footer is positioned and has a zindex of (for instance) 1, then your modal will appear behind everything on the same level of the DOM tree as the footer, which also has a higher zindex value. If your <main> tag is positioned and has a zindex of 2, your modal will not be able to appear in front of it because, in the context of the footer parent, it is stacked below.

There are a couple of ways to resolve issues like these, but often the best way is to reconsider how your solution is built and why you're needing to have it built this particular way. If there's a reason your footer has a zindex of 1, then changing that just so that the modal works is unlikely to be an option. Generally, the answer is going to lie in restructuring your markup: moving your example modal up the DOM and outside of the footer stacking context so that it can compete with the 2 value applied to <main> directly.

Another way to tackle an issue like this is by removing the position declaration on your footer, or by upping the zindex. Obviously, if there's a reason you had a specific zindex or position declaration on your footer, this isn't the way to go.

So, although convoluted, context is not a hugely complex problem to solve. It can definitely be tricky to root out though don't worry though, we're about to get into the good stuff.


Opacity and Transforms.

And here is where things become most frustrating and complex: opacity and transform declarations create a new stacking context for your element. W3 offers up an explanation of the way transparency affects zindex in their docs. The reason that this is where things become tricky is that if you're running into this problem, then chances are high that you are already working on a more advanced or complex layout.

New stacking contexts can be created by opacity and by transforms? How does that affect the positioning point from above? Well, whilst applying position to an element will resolve many zindex issues, it isn't totally true that you have to have a position declaration in order for zindex to work. Zindex works based on stacking contexts, and doesn't really care how those contexts are created, so if you can create a context without position (hint: you can), then zindex will still work, sometimes quite unexpectedly...

For example:

.box {  height: 20rem;  width: 20rem;  &--green {    background: lime;    z-index: 2;    opacity: 0.9;  }  &--red {    background: red;    z-index: 1;    margin: -10rem 0 0 7.5rem;  }}

Results in this:

Screenshot demonstrating CSS z-index quirks when combined with opacity and negative margins.

In this instance, the green box (.boxgreen) is appearing above the red box even though there's no positioning applied to either of them and .boxred follows .boxgreen in the markup, and it's all to do with that opacity declaration causing the green box to pull upwards in the stacking context.


The Wrap‑Up

For something that should be so simple, Zindex can be a deceptively complex declaration, and there are bits and pieces to bear in mind when you're working with it. As long as you get the hang of stacking contexts, you're mostly going to be fine however, getting to grips with those stacking contexts can be surprisingly difficult.

If you're running into a weird issue with stacking, it's worth refreshing yourself with a list of properties that can create stacking contexts.

If you take nothing else away from this article, this I feel is the most important thing to understand about zindices:

Zindex is not a system for defining global layers throughout a document. It only differentiates layer ordering for elements within those element's shared, positioned, parent.


Categories:

  1. CSS
  2. Development
  3. Front‑End Development