Who's not Afraid of z-index?
Before I found out, thanks to CSS for JS Devas class, that the z-index had a logic behind it, I used to always fall into the z-index hell, trying to put one element on top of the other. This drove me to desperate measures, like the z-index: 9999999999999, and that’s why in this post I’ll show you good and bad practices with z-index, so that you can finally go to z-index heaven :)
What is it
Z-index is a CSS property that we use to alter the standard order of the HTML layers. The standard behavior of the browser is to render the next element in front of the last one. This way, the div with the class box__1 will be in the last layer of the pile while the div with the class box__3 will be on the first layer of that pile.

<div class="box box__1">1</div>
<div class="box box__2">2</div>
<div class="box box__3">3</div>
Imagine that the HTML is a pile of layers and that z-index is used to set a new order for the elements in this stack. This means that, the higher the z-index value, the higher their position on the stack, therefore the more in front it is going to appear.
<style>
.box{ position: relative }
.box__1 { z-index: 1 }
.box__2 { z-index: 1000 }
.box__3 { z-index: 2 }
</style>
<div class="box box__1" >1</div>
<div class="box box__2">2</div>
<div class="box box__3">3</div>
The code above will result in something like the image below, because the div, with the class box__2 has the higher z-index.
Where and when to use it
In the z-index hell, you’ll find a situation where not all elements respect the position you defined, as the codepen below.
In a situation like this, we usually enter into a duel with the z-index values ranging from 100 to some random gargantuan number that you chose to try to fix the conflict in the element’s order. To really fix it, you have to understand that:
- Z-index is applied in all contexts, so if you don’t define a context, it will use the DOM’s global context;
- As a standard, the HTML elements have a
positionproperty, defined asposition: static; - To apply the
z-index, it is mandatory to change the positioning of the elements to one of the following:position: relative | absolute | sticky | fixed. Besides that, thez-indexproperty works without the need to change the standard positioning of the elements in thegridandflexlayouts; - If the
z-indexvalue is not assigned, the element will use the standard order of the DOM; - The highest possible value accepted by the
z-indexproperty is 2147483648, in other words, the highest number that can be stored in 32bits. If a value is higher than that, it will be ignored.
Bad uses
In a large application, z-index can be an even larger problem, because you can have several elements with the z-index explicitly declared. In this case, you might affect some other element when trying to alter these z-index’d elements.
Usually in this situation my approach used to be trying to use random values until I found something that fit perfectly in the context. This was a real work around, that relied on luck and made get to situations like the one below.
Good uses
But you don’t have to go through that, there are paths to avoid these problems! Down here, I’ll show you different approaches to avoid the z-index hell.
Isolation: isolate
The isolation property is supported by all browsers, except IE. As a standard, its value is auto, allowing the z-index of the sons of an element to mix with other elements from the DOM.
When we use the value isolate, we start isolating the z-index of the sons of that element in its own context. With that, you can declare, for example, z-index: 1000 in a son and this value will be considered only on the pile of elements in the same context, avoiding the problem above. In the code below, I isolate the header and post elements in a context so that they don’t stay in front of the modal. See in the codepen:
CSS variables
Another approach broadly used is the creation of variables for the z-index layers. This approach is used by Bootstrap and is based on the creation of “breakpoints” that guide the elements' priority in the layers of the z-index. For example, a modal that needs to be on top of a dropdown, its variable will have a larger value that the one of the dropdown. This prevents the team uses random values.
/* css variables */
$zindex-flow: 100;
$zindex-modal-backdrop: 1040;
$zindex-modal: 1060;
/* css variables */
--zindex-flow: 100;
--zindex-modal-backdrop: 1040;
--zindex-modal: 1060;
Tailwind approach
Tailwind uses the approach of applying classes to the element that needs an explicit z-index. With it, the team will have a clear view of the position of the element in the pile with indexes that go from z-0 to z-50 as a standard, able to be overwritten using z-[100].
$zIndex: (
"0": 0,
"10": 10,
"20": 20,
"30": 30,
"40": 40,
"50": 50,
"60": 60,
"70": 70,
"80": 80,
"90": 90,
"100": 100,
"auto": "auto",
);
@each $index, $level in $zIndex {
.z-#{$index} {
`z-index`: $level;
}
}
Portals
Portals is an approach available in several frameworks, like Angular, and libraries, like React, to deal with z-index problems. It is an approach more recommended to UI libraries that use dropdown, modal and other priority components in the z-index. So you probably won’t need to use this kind of solution in your projects, but it is still important to know that it exists. It creates a div below the standard div in which the content is rendered, assigns the rule position: relative to that div and a z-index with a high value. On React, this value is 999.
Conclusion
Is there any questions that you have? Do you know a different approach from the ones I talked about here? Feel free to comment below and let’s get talking! :))