Anyone who has ever worked with HTML and CSS may have heard of, or worked with the z-index property. What it does is pretty straightforward: It allows you to manage the stacking order of so called “positioned” HTML elements. Anything with a higher z-index number should always appear above elements with a lower z-index. However, sometimes you end up in situations where the z-index property doesn’t seem to have any effect at all and it isn’t that straightforward as it seems. In these situations it’s likely that you’re dealing with differences in the stacking context.
Default rules of stacking elements
When no positioned elements are applied, the basic rules of the element stacking order is as follows:
- When the root element <html> is rendered, it creates the root (or global) stacking context.
- Followed by non-positioned elements, where the position value is not specified. The position value defaults to static.
- Followed by positioned elements, which are all elements with a position property other than static, such as fixed, absolute, sticky or relative.
All respecting the order they are declared in the document.
Stacking with positioned elements
When elements are positioned, they appear above non-positioned elements. Many layouts consist out of positioned elements where controlling the stacking order of elements is required. A common use case is for instance a modal you want to show above the normal content of your page. To control the stacking order, z-index is used on positioned elements. Z-index won’t work on non-positioned elements. Applying the position properties with values like fixed and sticky, or absolute and relative in combination with a z-index value other than auto, will create a new local stacking context.
Taking (local) stacking contexts into account
Things get more tricky now. When stacking contexts are created on an element, that element will become a root stacking context element for its children elements. Documents can have many stacking contexts, so it is not rare to have stacking contexts inside other stacking contexts. The problem this creates is that children of the newly created stacking context won’t be able to render on top of an element in a different stacking context. Never ever. Even when the children will become positioned elements. Even with z-index: 999999;.
Implicit creation of stacking context
Another important thing to mention is that there are more implicit ways of how stacking contexts are created. Earlier in this post, I mentioned one way of how stacking contexts are created. There are more CSS properties that create their own stacking contexts implicitly. Unfortunately there is no clear way to see when a stacking context is created at a glance. However, there is a cool Chrome plugin that can be a bit of a help when identifying stacking context elements. It basically collects the properties that cause the stacking context for each element and outputs information about the element like this (shown in the right column, Z-index tab):
Properties that also create stacking contexts are (source from MDN):
- Element that is a child of a flex (flexbox) container, with z-index value other than auto.
- Element that is a child of a grid (grid) container, with z-index value other than auto.
- Element with an opacity value less than 1.
- Element with a mix-blend-mode value other than normal.
- Element with any of the following properties with a value other than none:
- mask / mask-image / mask-border
- Element with an isolation value isolate.
- Element with a -webkit-overflow-scrolling value touch.
- Element with a will-change value specifying any property that would create a stacking context on non-initial value.
- Element with a contain value of layout, or paint, or a composite value that includes either of them (i.e. contain: strict, contain: content).
Why these properties have side-effects like these, is unclear.
Solving it in when using React
Most React applications can consist of those more complex DOM hierarchies, which won’t allow you to swap out elements with their own stacking context so easily. However, React incorporated a neat addition called “Portals” in v16. Portals will allow you to mount any DOM element at any place in your DOM structure. React also mentions this in their documentation:
“A typical use case for portals is when a parent component has an overflow: hidden or z-index style, but you need the child to visually “break out” of its container. For example, dialogs, hovercards, and tooltips.“
I hope this helped you prevent future headaches by learning about stacking contexts and the way they interact with z-indexes. Here is a summary of this post:
- Applying certain CSS properties will allow you to control the stacking context.
- Applying a z-index value of the highest possible value number to a local stacking context won’t make that element appear above other elements with stacking contexts living higher up in the DOM.
- Z-indexes only have meaning in their self-contained stacking context (global or local)
- Layouts can have many stacking contexts alongside each other, and inside each other.
Thanks for reading and good luck stacking!