CSS: z-index and its management in projects
Almost every web developer knows something about the z-index property from the CSS standard. How nice would it be if all we-developers knew everything about z-index?
I know W3C documents are not for everyone. That’s why I read the official specification for you and present the most important facts about the topic here.
Let’s start without z-index and without positioning. Because even then HTML elements have a hierarchy. Let’s have a look at an example of this directly:
The block elements (div) are actually one below the other. By repositioning them with a negative margin (outside distance), the hierarchy can be easily recognized, since the elements overlap. The order corresponds to the element order in the source code. The browser draws the HTML code from top to bottom, so the last element is at the top of the stack.
With the z-index you can now influence the stacking order. For the value of z-index to be evaluated by the browser at all, the corresponding element must be positioned. That means its CSS property position must not have the initial value static, but must be relative, absolute or fixed.
Faust Rule: z-index only works together with position!
The z-index accepts only integer values. Therefore it seems to be quite obvious what happens when you rearrange positioned layers. Elements with a smaller z-index are behind elements with a larger z-index. Unfortunately it is a bit more complex.
If you follow the rule of thumb above, you always create a new stack context on the element that gets these properties. All child elements of this element are “trapped” in this layer and cannot be stacked below the parent element or ancestors of the parent element with smaller or equal z-index. Here is an example:
Some people might expect in the example that the yellow square is below the gray square because of its z-index value. This is not the case with the new stacking context, which is based on the parent-child relationship of the two elements and the absolute positioning in combination with the z-index. Here z-index: 0; then the lowest level within this context.
In the Mozilla Developer Network there is a comprehensive listing which elements create a stack context. Beside special cases in certain browser-device-combinations and some rather rarely used CSS3-features, there are some properties that stand out, which are used more often. Interestingly, opacity values below 1 and transform values except none also create a stacking context.
Basic rule: Any element can form a stacking context under certain circumstances. If a stacking context exists, child elements in the element are bound to this level in the hierarchy.
It is important that you understand how a Stacking Context is formed and what it means for the rendering in the browser.
Stacking order within a stacking context
In each Stacking Context, the layers are drawn in the following order (from back to front)
- The background and frame of the element forming the stacking context
- Elements with stacking context and negative z-index (highest minus values first)
- All block elements in the text flow that are not positioned
- Not positioned floating elements
- All non-positioned inline elements in the text flow (including inline tables and inline block elements)
- Elements with stacking context on level 0 (if necessary without z-index specification)
- Elements with stacking context and z-index higher than 0 (smallest values first)
Children of elements without stacking context
If an element has children and does not itself form a stacking context, but is a child of a stacking context, it is possible to put the children of this element behind its parent element. Example:
Management of z-index
As soon as several people are involved in a project and work on different aspects, it can become complicated to keep track of all z-index values. Since the history of the development is often not known, questions arise:
- Why does the element have exactly this z-index value?
- In which level is the element exactly?
- What effect would a change in value have on other elements?
- Which z-index values do I have to change as well when changing one value?
The basic idea of a possible solution
Jackie Balzer suggests using the index function of SASS. The idea is to define a list with the elements that should get a z-index value. The position in the list reflects the desired hierarchy on the website.
[prism field=ass-list language=scss class=line-numbers]
The list is simply stored in a variable ($elements). Into the index function we pass the list and the element whose position we want to know. The return value is then the position specification as a number, starting with 1. As a result we get an automatic assignment of the z-index value starting with the value 1.
[prism field=ass-list-result language=scss class=line-numbers]
If now in a later development phase a social bar is to be inserted between header and mega-dropdown, the great advantage of this approach becomes apparent. Only the list is extended at the appropriate place and all affected z-index values are automatically adjusted.
Consideration of stack context hierarchies
Since the z-index value can start at 1 within each stack context, you could create a list of corresponding subelements for each element. These should then also be child elements of a container in HTML.
The existing example is supplemented by the social bar and a new level (modal elements).
[prism field=ass-list hierarchy language=scss class=line-numbers]
The SASS compiler will spit out the following additional CSS.
[prism field=ass-list-hierarchy-result language=scss class=line-numbers]
Feasty rule of thumb: The system will only work if you stick to it consistently and let all create z-index values via the lists.
What about external CSS sources?
If you use standard solutions in your project (e.g. layers, dropdowns, etc.) that also use CSS, the following scenarios exist:
- I integrate the third party CSS myself!
Include the CSS as SCSS in your SCSS sources (e.g. in a “vendor” folder) and replace all z-index occurrences with the z-function.
- The third-party CSS is external!
- I integrate the third party CSS myself!
- Either you manually ensure that the fixed z-index values do not conflict with the automatically generated values, or …
- … insert the z-index affected elements into the lists and overwrite the selectors with the automatically generated z-index
Removal and error handling
The lists for all stack contexts should be maintained in a central location. A similar location as used for global color specifications is suitable for this. In addition, a help function could be defined there that provides rudimentary error handling.
[prism field=z-index-function language=scss class=line-numbers]
The list variable should perhaps be named according to the semantics of the respective stack context instead of $elements. What is appropriate for this always depends on the individual case. If the function returns zero, the compiler completely ignores the z-index property and does not output it at all.
Many thanks to Jackie Balzer for this useful function!