The z-index property is among the most vital instruments any UI developer has at their disposal, because it means that you can 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 sources deal with the technical particulars or the widespread pitfalls of the Stacking Context (we’ll get to that in a second…), I feel they miss some of the vital and doubtlessly chaotic points of z-index: the worth.

In most initiatives, when you hit a sure measurement, the z-index values develop into a large number of “magic numbers”, a chaotic battlefield of values, the place each group tries to outdo the others with increased 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 ponder why they selected that particular worth?” Once I requested the creator, they mentioned: “Properly, I simply needed to ensure it was above all the opposite components on the web page, so I selected a excessive quantity.”
This bought me excited about how we have a look at the stacking order of our initiatives, how we select z-index values, and extra importantly, the implications of these selections.
The Concern of Being Hidden
The core challenge isn’t a technical one, however an absence of visibility. In a big venture with a number of groups, you don’t at all times know what else is floating on the display screen. There could be a toast notification from Staff A, a cookie banner from Staff 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 will probably be on high.”
That is how we find yourself with magic numbers, these arbitrary values that aren’t linked to the remainder of the applying. 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 at the start, there are lots of sources that cowl z-index within the context of the Stacking Context. On this article, we received’t cowl that subject. Nonetheless, it’s unattainable to speak about z-index values with out no less than mentioning it, because it’s a vital idea to grasp.
Basically, components with the next 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 in case you set a large z-index worth on a component in a “decrease” stack, components in a “increased” stack will keep on high of it, even when they’ve a really low z-index worth. Which means that generally, even in case you give a component the utmost attainable worth, it might 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. If you happen to attempt to go any increased, 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: While you see a
z-indexworth like10001, it doesn’t let you know 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 may find yourself conflicting with one another, resulting in sudden conduct the place some components are hidden behind others. - Tough to debug: When one thing goes flawed with the stacking order, it may be difficult to determine why, particularly if there are lots of components with excessive
z-indexvalues.A Higher Method
I’ve encountered this “arms race” in virtually each massive venture I’ve been part of. The second you have got a number of groups working in the identical codebase and not using a standardized system, chaos finally 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 may roll their eyes or shake their heads, however this method really works. Many of the main (and better-designed) design techniques embody z-index tokens for a motive. Groups that undertake them swear by them and by no means look again.
Through the use of tokens, you achieve:
- Easy and straightforward upkeep: You handle values in a single place.
- Battle prevention: No extra guessing if
100is increased than no matter Staff B is utilizing. - Simpler debugging:: You may see precisely which “layer” a component belongs to.
- Higher Stacking Context administration: It forces you to consider layers systematically fairly than as random numbers.
A Sensible Instance
Let’s have a look at how this works in follow. I’ve ready a easy demo the place we handle our layers via 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 is advisable to add a brand new popup or a toast, you understand 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 via dozens of recordsdata. You simply change the values within the :root, and all the pieces updates accordingly in a single place.
Dealing with New Parts
The true energy of this method shines when your necessities change. Suppose it is advisable to add a brand new sidebar and place it particularly between the bottom content material and the toasts.
In a standard setup, you’d be checking each present factor to see what numbers they use. With tokens, we merely insert a brand new token and alter 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 present 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 generally need to “lock” particular layers relative to one another. A terrific instance of it is a background factor 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 at all times belong collectively:
.overlay-background {
z-index: calc(var(--z-overlay) - 1);
}
This ensures that the background will at all times 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, international layers of the applying. However what occurs inside these layers?
The tokens we created for the principle layers (like 100, 200, and so on.) usually are not appropriate for managing inside components. It is because most of those primary elements create their very own Stacking Context. Inside a popup that has z-index: 300, a price of 301 is functionally similar to 1. Utilizing massive international tokens for inside positioning is complicated and pointless.
Observe: For these native tokens to work as anticipated, it’s essential to make sure the container creates a Stacking Context. If you happen to’re engaged on a part that doesn’t have already got one (e.g., it doesn’t has a z-index set), you possibly can create one explicitly utilizing isolation: isolate.
To unravel this, we will introduce a pair of “native” tokens particularly for inside use:
:root {
/* ... international tokens ... */
--z-bottom: -10;
--z-top: 10;
}
This enables us to deal with inside positioning with precision. If you happen to 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 need to 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 possibly can nonetheless use calc() with these native tokens. In case you 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 have a look at international values.
This retains our logic constant: we take into consideration layers and positions systematically, fairly than throwing random numbers on the drawback and hoping for the perfect.
Versatile Parts: The Tooltip Case
One of many largest complications in CSS is managing elements that may seem wherever, like a tooltip.
Historically, builders give tooltips a large z-index (like 9999) as a result of they may seem over a modal. But when the tooltip is bodily contained in the modal’s DOM construction, its z-index is simply relative to that modal anyway.
A tooltip merely must be above the content material it’s hooked up to. Through the use of our native tokens, we will cease the guessing sport:
.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 would at all times seem accurately above its instant environment. It doesn’t must know in regards to the international “arms race” as a result of it’s already standing on the “steady ground” supplied by its dad or mum layer’s token.
Unfavourable Values Can Be Good
Unfavourable values usually scare builders. We fear that a component with z-index: -1 will disappear behind the web page background or some distant dad or mum.
Nonetheless, inside our systematic method, adverse values are a strong instrument 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:
- Element backgrounds: Delicate patterns or gradients that shouldn’t intrude with textual content.
- Shadow simulations: While you want extra management than
box-shadowoffers. - Inside 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 means to make sure that managing layers by no means looks like a guessing sport 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 world layer token or an area positioning token. - It’s hardly ever the worth: If a component isn’t showing on high regardless of a “excessive” worth, the issue is sort of actually its Stacking Context, not the quantity itself.
- Suppose 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) fairly 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 simply nearly as good as its enforcement. In a deadline-driven atmosphere, it’s simple for a developer to slide in a fast z-index: 999 to “make it simply work”. With out automation, your lovely token system will finally erode again into chaos.
To stop 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 offers a unified set of instruments to robotically 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 recordsdata instantly or be built-in into your CI/CD pipelines
Through the use of these instruments, you flip the “Golden Guidelines” from a suggestion into a tough requirement, making certain that your codebase stays clear, scalable, and, most significantly, predictable.









