The z-index property is likely one of the most essential instruments any UI developer has at their disposal, because it permits you to management the stacking order of components on a webpage. Modals, toasts, popups, dropdowns, tooltips, and plenty of different widespread components depend on it to make sure they seem above different content material.
Whereas most assets give attention to the technical particulars or the widespread pitfalls of the Stacking Context (we’ll get to that in a second…), I feel they miss one of the crucial essential and probably chaotic features of z-index: the worth.
In most initiatives, when you hit a sure measurement, the z-index values develop into a multitude of “magic numbers”, a chaotic battlefield of values, the place each workforce tries to outdo the others with larger and better numbers.
How This Thought Began
I noticed this line on a pull request just a few years in the past:
z-index: 10001;
I assumed to myself, “Wow, that’s an enormous quantity! I’m wondering why they selected that particular worth?” Once I requested the creator, they mentioned: “Properly, I simply wished to verify it was above all the opposite components on the web page, so I selected a excessive quantity.”
This obtained me desirous about how we take a look at the stacking order of our initiatives, how we select z-index values, and extra importantly, the implications of these decisions.
The Concern of Being Hidden
The core difficulty isn’t a technical one, however a scarcity of visibility. In a big venture with a number of groups, you don’t all the time know what else is floating on the display. There may be a toast notification from Workforce A, a cookie banner from Workforce B, or a modal from the advertising SDK.
The developer’s logic was easy on this case: “If I exploit a extremely excessive quantity, absolutely it will likely be on high.”
That is how we find yourself with magic numbers, these arbitrary values that aren’t related to the remainder of the appliance. They’re guesses made in isolation, hoping to win the “arms race” of z-index values.
We’re Not Speaking About Stacking Context… However…
As I discussed in the beginning, there are numerous assets that cowl z-index within the context of the Stacking Context. On this article, we gained’t cowl that subject. Nonetheless, it’s not possible to speak about z-index values with out not less than mentioning it, because it’s an important idea to grasp.
Primarily, components with a better z-index worth will likely be displayed in entrance of these with a decrease worth so long as they’re in the identical Stacking Context.
In the event that they aren’t, then even when you set a large z-index worth on a component in a “decrease” stack, components in a “larger” stack will keep on high of it, even when they’ve a really low z-index worth. Which means that typically, even when you give a component the utmost attainable worth, it will probably nonetheless find yourself being hidden behind one thing else.
Now let’s get again to the values.
💡 Do you know? The utmost worth for z-index is 2147483647. Why this particular quantity? It’s the utmost worth for a 32-bit signed integer. In case you attempt to go any larger, most browsers will merely clamp it to this restrict.
The Downside With “Magic Numbers”
Utilizing arbitrary excessive values for z-index can result in a number of points:
- Lack of maintainability: If you see a
z-indexworth like10001, it doesn’t inform you something about its relationship to different components. It’s only a quantity that was chosen with none context. - Potential for conflicts: If a number of groups or builders are utilizing excessive
z-indexvalues, they could find yourself conflicting with one another, resulting in surprising habits the place some components are hidden behind others. - Tough to debug: When one thing goes unsuitable with the stacking order, it may be difficult to determine why, particularly if there are numerous components with excessive
z-indexvalues.A Higher Strategy
I’ve encountered this “arms race” in virtually each massive venture I’ve been part of. The second you may have a number of groups working in the identical codebase and not using a standardized system, chaos ultimately takes over.
The answer is definitely fairly easy: tokenization of z-index values.
Now, wait, stick with me! I do know that the second somebody mentions “tokens”, some builders would possibly roll their eyes or shake their heads, however this strategy really works. Many of the main (and better-designed) design programs embrace z-index tokens for a purpose. Groups that undertake them swear by them and by no means look again.
By utilizing tokens, you acquire:
- Easy and simple upkeep: You handle values in a single place.
- Battle prevention: No extra guessing if
100is larger than no matter Workforce B is utilizing. - Simpler debugging:: You’ll be able to see precisely which “layer” a component belongs to.
- Higher Stacking Context administration: It forces you to consider layers systematically slightly than as random numbers.
A Sensible Instance
Let’s take a look at how this works in apply. I’ve ready a easy demo the place we handle our layers by way of a central set of tokens within the :root:
:root {
--z-base: 0;
--z-toast: 100;
--z-popup: 200;
--z-overlay: 300;
}
This setup is extremely handy. If it’s essential to add a brand new popup or a toast, precisely which z-index to make use of. If you wish to change the order — for instance, to put toasts above the overlay — you don’t must hunt by way of dozens of information. You simply change the values within the :root, and the whole lot updates accordingly in a single place.
Dealing with New Parts
The true energy of this technique shines when your necessities change. Suppose it’s essential to add a brand new sidebar and place it particularly between the bottom content material and the toasts.
In a conventional setup, you’d be checking each current aspect to see what numbers they use. With tokens, we merely insert a brand new token and modify the size:
:root {
--z-base: 0;
--z-sidebar: 100;
--z-toast: 200;
--z-popup: 300;
--z-overlay: 400;
}
You don’t have to the touch a single current part with this setup. You replace the tokens and also you’re good to go. The logic of your utility stays constant, and also you’re now not guessing which quantity is “excessive sufficient”.
The Energy of Relative Layering
We typically wish to “lock” particular layers relative to one another. A terrific instance of it is a background aspect for a modal or an overlay. As an alternative of making a separate token for the background, we will calculate its place relative to the principle layer.
Utilizing calc() permits us to take care of a strict relationship between components that all the time belong collectively:
.overlay-background {
z-index: calc(var(--z-overlay) - 1);
}
This ensures that the background will all the time keep precisely one step behind the overlay, it doesn’t matter what worth we assign to the --z-overlay token.
Managing Inner Layers
Up till now, we’ve centered on the principle, world layers of the appliance. However what occurs inside these layers?
The tokens we created for the principle layers (like 100, 200, and many others.) will not be appropriate for managing inside components. It’s because most of those fundamental elements create their very own Stacking Context. Inside a popup that has z-index: 300, a worth of 301 is functionally equivalent to 1. Utilizing massive world tokens for inside positioning is complicated and pointless.
Notice: For these native tokens to work as anticipated, it’s essential to make sure the container creates a Stacking Context. In case you’re engaged on a part that doesn’t have already got one (e.g., it doesn’t has a z-index set), you may create one explicitly utilizing isolation: isolate.
To unravel this, we will introduce a pair of “native” tokens particularly for inside use:
:root {
/* ... world tokens ... */
--z-bottom: -10;
--z-top: 10;
}
This enables us to deal with inside positioning with precision. In case you want a floating motion button inside a popup to remain on high, or an ornamental icon on a toast to sit down behind the principle content material, you should use these native anchors:
.popup-close-button {
z-index: var(--z-top);
}
.toast-decorative-icon {
z-index: var(--z-bottom);
}
For much more complicated inside layouts, you may nonetheless use calc() with these native tokens. You probably have a number of components stacking inside a part, calc(var(--z-top) + 1) (or - 1) provides you that further little bit of precision with out ever needing to take a look at world values.
This retains our logic constant: we take into consideration layers and positions systematically, slightly than throwing random numbers on the drawback and hoping for the most effective.
Versatile Parts: The Tooltip Case
One of many largest complications in CSS is managing elements that may seem anyplace, like a tooltip.
Historically, builders give tooltips a large z-index (like 9999) as a result of they could seem over a modal. But when the tooltip is bodily contained in the modal’s DOM construction, its z-index is just relative to that modal anyway.
A tooltip merely must be above the content material it’s hooked up to. By utilizing our native tokens, we will cease the guessing recreation:
.tooltip {
z-index: var(--z-top);
}
Whether or not the tooltip is on a button in the principle content material, an icon inside a toast, or a hyperlink inside a popup, it can all the time seem appropriately above its instant environment. It doesn’t must know in regards to the world “arms race” as a result of it’s already standing on the “steady ground” supplied by its mum or dad layer’s token.
Destructive Values Can Be Good
Destructive values usually scare builders. We fear that a component with z-index: -1 will disappear behind the web page background or some distant mum or dad.
Nonetheless, inside our systematic strategy, detrimental values are a robust device for inside decorations. When a part creates its personal Stacking Context, the z-index is confined to that part. And z-index: var(--z-bottom) merely means “place this behind the default content material of this particular container”.
That is good for:
- Part backgrounds: Refined patterns or gradients that shouldn’t intrude with textual content.
- Shadow simulations: If you want extra management than
box-shadowgives. - Interior glows or borders: Parts that ought to sit “underneath” the principle UI.
Conclusion: The z-index Manifesto
With only a few CSS variables, we’ve constructed an entire administration system for z-index. It’s a easy but highly effective approach to make sure that managing layers by no means seems like a guessing recreation once more.
To take care of a clear and scalable codebase, listed below are the golden guidelines for working with z-index:
- No magic numbers: By no means use arbitrary values like
999or10001. If a quantity isn’t tied to a system, it’s a bug ready to occur. - Tokens are obligatory: Each
z-indexin your CSS ought to come from a token, both a worldwide layer token or a neighborhood positioning token. - It’s not often the worth: If a component isn’t showing on high regardless of a “excessive” worth, the issue is nearly actually its Stacking Context, not the quantity itself.
- Assume in layers: Cease asking “how excessive ought to this be?” and begin asking “which layer does this belong to?”
- Calc for connection: Use
calc()to bind associated components collectively (like an overlay and its background) slightly than giving them separate, unrelated tokens. - Native contexts for native issues: Use native tokens (
--z-top,--z-bottom) and inside stacking contexts to handle complexity inside elements.
By following these guidelines, you flip z-index from a chaotic supply of bugs right into a predictable, manageable a part of your design system. The worth of z-index isn’t in how excessive the quantity is, however within the system that defines it.
Bonus: Implementing a Clear System
A system is just nearly as good as its enforcement. In a deadline-driven atmosphere, it’s straightforward for a developer to slide in a fast z-index: 999 to “make it simply work”. With out automation, your lovely token system will ultimately erode again into chaos.
To forestall this, I developed a library particularly designed to implement this precise system: z-index-token-enforcer.
npm set up z-index-token-enforcer --save-dev
It gives a unified set of instruments to routinely flag any literal z-index values and require builders to make use of your predefined tokens:
- Stylelint plugin: For traditional CSS/SCSS enforcement
- ESLint plugin: To catch literal values in CSS-in-JS and React inline types
- CLI scanner: A standalone script that may shortly scan information immediately or be built-in into your CI/CD pipelines
By utilizing these instruments, you flip the “Golden Guidelines” from a advice into a tough requirement, making certain that your codebase stays clear, scalable, and, most significantly, predictable.
