The Quirks of z‑index

Z‑index is one of those classic examples of easy‑to‑forget CSS that nonetheless can frustrate even good developers to no end. It's not uncommon to see elements with z‑index declarations like z‑index: 9999 !important; on them, even though the layouts in question might not look overly complex.
So, what's up? Why can z‑index be such a fiddly and easily misused and misunderstood declaration? It all comes down to a few major quirks that come with the z‑index 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 z‑index stacks, the first thing to check is your positioning declarations. It is perhaps a little counterintuitive, but z‑index 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.
Z‑index 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 z‑index 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 z‑index, 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 z‑index 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 z‑index 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 z‑index value. If your <main> tag is positioned and has a z‑index 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 z‑index 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 z‑index. Obviously, if there's a reason you had a specific z‑index 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 z‑index 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 z‑index issues, it isn't totally true that you have to have a position declaration in order for z‑index to work. Z‑index 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 z‑index 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:

In this instance, the green box (.box‑‑green) is appearing above the red box ‑ even though there's no positioning applied to either of them and .box‑‑red follows .box‑‑green 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, Z‑index 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 z‑indices:
Z‑index 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:
Related Articles
How to Rename Local and Remote Git Branches. 
Positioning in CSS. Positioning in CSS

Single Number in TypeScript with Bit Manipulation. Single Number in TypeScript with Bit Manipulation

Advanced Techniques for Responsive Web Design. Advanced Techniques for Responsive Web Design

The CSS overflow Property. The CSS
overflowProperty
Generate Parentheses in TypeScript: A Clean Backtracking Walkthrough. Generate Parentheses in TypeScript: A Clean Backtracking Walkthrough

Dynamic Array Manipulation with JavaScript's splice(). Dynamic Array Manipulation with JavaScript's
splice()
Testing Vue Components with Vue Test Utils. Testing Vue Components with Vue Test Utils

Mutation vs. Immutability in JavaScript Arrays and Objects. Mutation vs. Immutability in JavaScript Arrays and Objects

Can I Learn Front‑End Development in 2 Months? Can I Learn Front‑End Development in 2 Months?

Using data‑* Attributes and dataset in JavaScript. Using
data‑*Attributes anddatasetin JavaScript
Single or Double Colons in CSS Pseudo‑Elements (:before vs. ::before). Single or Double Colons in CSS Pseudo‑Elements (
:beforevs.::before)