Z index hero 02

Man­ag­ing Z‑Index in a Design System

What else is left after writ­ing z‑index: 9999” for the last time? A man­aged approach to z‑index using cus­tom css variables.





Background

Z-index is often one of the last lines of css I’ll write before a module is done. But lately, I’ve applied some systems thinking to improve the developer experience. What follows is a very simple framework that leverages custom css variables to create a single source of truth for css modules that use z-index.

This system adapted from this article. If you want to learn how to do the same thing using css custom variables, read on reader!

CSS Variables Documentation and Redundancy

What’s useful about this system is that it improves the developer experience two ways: by providing documentation for all the z-indexes used in a project, and helps to prevent redundancy and overlap in your code base.

We’ll cover the basics of this framework, then launch into how it could be implemented and expanded upon within a design system. Finally, we’ll test it out on an example site using custom css variables.

In our example, we’ll be positioning a sticky header over a main element, a footer beneath that main element. Also, we'll have a nav item that persists underneath the header but above the main element, and a modal in the highest stacking context.

Yes, sounds confusing! With this framework we'll simplify these stacking contexts with some human-readable documentation.

Introducing the Z-Index Stacking Context

The first place we start with z-index is the default value auto, which when applied to the stacking context equals zero.

--z-base: 0

Check the Specs

Let's take a moment here to refer to the z-index specifications:

Each box belongs to one stacking context. A stacking context is atomic from the point of view of its parent stacking context.

This means that for each child element, the stacking element therein restarts at zero. That means we'll need an above and below modifier to add or subtract from the stack level.

Note that throughout this framework we’ll be prefixing with “z-” to prevent any overwriting of css variables in your project.

--z-base: 0 --z-above: 1 --z-below: -1

Setting up Base

Once we have our basic operating integers, let’s apply them to some of our modules.

For our header, we want that above base. Our footer will tuck behind the base for a little "peek" effect at the bottom of the page.

// Layout --z-main: var(--z-base) --z-header: var(--z-above) + var(--z-base) --z-footer: var(--z-below) + var(--z-base)

Adding Relative Stacking Contexts

Now we need to work in a nav that will fit nicely below the header, but above main. The toggle for that nav will have a separate stacking context, inside the header element. Then we'll account for our modal, which will have to go at the top of our stacking context.

Instead of adding another digit to an arbitrary integer, we can leverage our framework to make the actual value irrelevant by basing the value on the header instead.

// Modules --z-nav: var(--z-below) + var(--z-header) --z-nav-toggle: var(--z-above) + var(--z-header) --z-modal: var(--z-above) + var(--z-nav-toggle)

Putting it All Together

Here’s our example framework all together. Note that the position of each line is relative to the stacking context of that module.

// Base --z-base: 0 --z-above: 1 --z-below: -1 // Layout --z-footer: var(--z-below) + var(--z-base) --z-main: var(--z-base) --z-header: var(--z-above) + var(--z-base) // Modules --z-nav: var(--z-below) + var(--z-header) --z-nav-toggle: var(--z-above) + var(--z-header) --z-modal: var(--z-above) + var(--z-nav-toggle)

Let's see it in context!

See the Pen Untitled by Cody (@codyhopper) on CodePen.

Summary

With this framework in place, we were able to simplify and document all the z-indexes used in this example site.

We made this system easily reproducible with css variables and reduced any redundancy by letting those variables be relative to one another. Further, this framework plays well with a site-wide stacking context, but could easily be adapted for inner module stacking contexts.

Let me know what you think of this solution by emailing me directly via my contact page. Thanks for reading!