Several weeks ago I held a workshop for my colleagues related to stacking context and z-index, so I decided to write about it here as well - as a beautiful reminder :D
Okey dokey, so... when a web page is loaded, the browser first reads the HTML text and constructs the Document Object Model (DOM) from it. Then it processes the CSS and constructs the CSS Object Model (CSSOM) from it. When you combine DOM and CSSOM, you get a render tree. Once the render tree is constructed, the browser starts printing the elements on the screen in a way they appear in HTML.
Every element in an HTML document can be either in front of or behind every other element in the document. This is known as the stacking order. When the z-index and position properties aren’t involved, the rules are simple: the stacking order is the same as the order of appearance in the HTML. This is called the natural stacking order.
Here we can see three divs without z-index and position applied. They are displayed on the page as their order of appearance in the HTML:
We can change the stacking order by adding the position property. Any positioned elements (and their children) are displayed in front of any non-positioned elements. Positioned elements have a position value relative, absolute, sticky or fixed. Non-positioned elements are the ones with default position (static).
The magenta div has a position relative so it is now in front of non-positioned cyan and yellow div:
It gets a bit trickier when we add z-index. z-index only works on positioned elements. If you try to set a z-index on a non-positioned element, it will do nothing.
z-index is added to the non-positioned yellow div, but it is still under the magenta div (which has position relative and no z-index) since z-index does not work on non-positioned elements:
However, there is one exception - flex children can use z-index even if they're non-positioned.
If we add display: flex on their parent, non-positioned yellow div with z-index will appear in front of positioned magenta div:
Elements with a bigger z-index will appear in front of the ones with a lower z-index, and if the element has a negative z-index it will appear behind non-positioned elements.
All divs have a position relative and z-index. Cyan div has the biggest z-index value so it is in front of the other 2, then comes the magenta div, and then the yellow one because it has the lowest z-index value:
By adding a z-index on positioned elements, we create a stacking context. A stacking context is a group of elements that have a common parent that moves forward or backwards together in the stacking order. Every stacking context has a single HTML element as its root element. Stacking context limits all of its child elements to a particular place in the stacking order which means that if an element is contained in a stacking context at the bottom of the stacking order, there is no way to get it to appear in front of another element in a different stacking context that is higher in the stacking order.
Magenta and yellow div are both position relative and have some z-index value. The element inside the magenta div has a bigger z-index but it appears behind the yellow div. It is part of the stacking context created by the magenta div behind the yellow div. z-index value is only compared against other elements in the same context and z-index values are not global:
So, to sum it all up - stacking contexts can be formed on an element in one of these ways:
- When an element is the root element of a document (the <html> element)
- When an element has a position value other than static and a z-index value other than auto
- When an element is a child of flex parent with a z-index value other than auto
- When an element has an opacity value of less than 1
- When an element has transform, filter, perspective, clip-path property value other than none