NK

Support Ukraine

Managing z-indexes within CSS-in-JS

Managing z-index can be challenging, as demonstrated in these common scenarios:

.header {
position: sticky;
z-index: 100; /* Keep sticky header above other content */
}

.popup {
z-index: 99999; /* Must be the absolute top */
}

In a well-established codebase, the z-index values may be disorganized, such as [-1, 1, 2, 3, 4, 5, 10, 11, 19, 100, 200, 299, ..., 99999], posing challenges in choosing the next value or inserting one in between.

Organizing z-index values in an array is a neat approach when using CSS-in-JS libraries (e.g. styled-components, Linaria, JSS, etc.).

const zIndexOrder = [
'body',
'header',
'popup',
];

const zIndexes = zIndexOrder.reduce(
(acc, current, index) => {
acc[current] = index;
return acc;
},
{}
);

export default zIndexes;

The array can be used in components as follows:

import styled from 'styled-components';
import zIndexes from '../style/zIndexes';

const MyHeader = styled.header`
z-index:
${zIndexes.header};
`
;

The advantage of this is approach is that adding a new z-index is simple:

const zIndexOrder = [
'body',
'aside', // New
'header',
'popup',
];

This keeps z-index values automatically updated.

TypeScript variant #

With TypeScript, you can use the following approach:

const zIndexOrder = [
'body',
'header',
'popup',
] as const;

type ZIndexValues = typeof zIndexOrder[number];
type ZIndexRecord = Record<ZIndexValues, number>;

const zIndexes = zIndexOrder.reduce(
(acc: ZIndexRecord, current: ZIndexValues, index: number) => {
acc[current] = index;
return acc;
},
{} as ZIndexRecord
);

export default zIndexes;

While it may seem complex, it ensures type safety in components:

import styled from 'styled-components';
import zIndexes from '../style/zIndexes';

// The following gives an error:
// TS2551: Property 'headr' does not exist on type 'ZIndexRecord'. Did you mean 'header'?
const MyHeader = styled.header`
z-index:
${zIndexes.headr};
`
;

Mind your stacking contexts #

A z-index is relative to other elements in a "stacking context," which the main root element (<html />) automatically creates. New stacking contexts can be created intentionally and unintentionally through various means listed on MDN.

Consequently, an element with a high z-index may appear behind an element with a low z-index, which can be confusing.

The most frequent cause of this is using position on a parent element while attempting to set a z-index on a child element.

Example:

<div class="red-parent">
<div class="red-child">
Red
</div>
</div>

<div class="blue-parent">
<div class="blue-child">
Blue
</div>
</div>
.red-parent {
position: absolute; /* this creates a new stacking context */
border: 2px dashed DarkRed;
}

.red-child {
padding: 2rem;
background-color: IndianRed;
z-index: 999; /* despite having a high value, red appears behind blue. */
}

.blue-parent {
position: absolute; /* this creates a new stacking context */
top: 4rem;
left: 3rem;
border: 2px dashed DarkBlue;
}

.blue-child {
padding: 2rem;
background-color: CornflowerBlue;
}

Blue on top of red

Despite the .red-child { z-index: 999 }, blue still appears above red because .red-parent { position: absolute } creates a new stacking context for .red-parent's children.

A new stacking context affects only the children of an element, not the element itself. Therefore, blue appears above red as both .blue-parent and .red-parent have a z-index of 0 in the same root stacking context, and the last HTML element drawn is on top.

A common solution is to move the z-index to the positioned element, such as:

.red-parent {
position: absolute;
border: 2px dashed DarkRed;
z-index: 999; /* move z-index to the positioned element */
}

.red-child {
padding: 2rem;
background-color: IndianRed;
/* remove z-index from child */
}

Red on top of blue

Why does this matter for the zIndexes-array approach? #

When using a single zIndexes-array, it corresponds to the root stacking context, but unexpected results may occur if multiple stacking contexts exist. To avoid this, limit z-indexes to main positioned elements and use a single array for the whole app. If a separate stacking context is needed, define a separate array for that context, rather than trying to manage all z-indexes in one array.

SASS #

The same approach can be applied using SASS, as shown in Handling z-index with SASS.

← Home