Everything you want to know about z-index

Aishwaryalakshmi Panneerselvam
8 min readDec 27, 2021

Every web developer would have faced a scenario where even after specifying a higher z-index value on an element, it will not show up at the front in the UI.

You would have been puzzled, “Why is my z-index not working?”. The short answer is “Stacking context”.

You can bring an element forward or backwards in many ways. z-index is not the only way to solve the requirement. That’s when our buddy stacking context comes into the picture.

If you have a solid understanding of how the stacking context works, you can solve any z-index issue easily.

This blog would bridge that knowledge gap. You will learn about stacking context, ways to create one and tips to choose the right CSS property for creating stacking context.

What is stacking context?

The MDN Docs says

The stacking context is a three-dimensional conceptualization of HTML elements along an imaginary z-axis relative to the user, who is assumed to be facing the viewport or the webpage. HTML elements occupy this space in priority order based on element attributes.

Visualize stacking context as a stack of folders arranged on a shelf. No matter what we do, we cannot move a single paper in one folder to the front without pulling it out from the folder. The folder’s position determines the placement of the paper.

Several folders, each one containing papers arranged in an order.

Similarly, the stacking context of a DOM element determines the arrangement of its children. Specifying a higher z-index on the child element will have no effect if the parent’s stacking context has lesser precedence over its siblings.

If things are still blurry, hang on. We will explore examples in the upcoming sections. You will get a clear picture of stacking context by the end of the article.

Ways to create stacking context

Stacking context with DOM order

In general, when no special CSS property is involved, the elements are stacked in the order they appear in the DOM.

In the example below, the three boxes are rendered in the order they appear in the DOM.

Stacking context with the position property

Let’s view a modified version of the above code pen. I have added 1, 2 and 3 in each of the colored boxes. The boxes are displaced from one another using margins.

Although the background and border are properly displaced, the number 1 bleeds out the container and extends into the orange box. It is because the browser algorithm paints the background and border first and the content after. That is why the content bleeds out.

You can correctly place box 2 in front of box 1 by creating a stacking context on 2. If you add position: relative to the second box, it creates a stacking context on this element. And now box 2 appears in front of 1 and 3.

An element with position relative stacks itself above the element with position static. The box with number 3 has position static (the default position value) and that’s why box 2 appears at the front.

In general, an element with a position value other than static (sticky, relative, fixed, absolute) is rendered on top of other statically positioned elements. The rendering follows the order given below.

  • Descendant positioned elements, in order of appearance in the HTML
  • Descendant non-positioned blocks, in order of appearance in the HTML
  • The background and borders of the root element

In the example below, we have elements containing the various position values.

We have a sticky header with a relatively positioned sibling. A static sidebar and an absolutely positioned main (The positioning is done hypothetically to demonstrate the behavior. A real-time scenario wouldn’t have this structure)

Try to scroll the output. The sticky header will slide under the relatively positioned element.

Now try to remove position:relativefrom the .relativeChildclass. You will see a different effect now. The header will slide on top of the yellow container.

Looks weird? Let’s try to understand what is happening here. Initially, the position values sticky on the header and relative on the .relativeChildclass created stacking context and position: relative is given higher precedence and placed on top as it is present later in the DOM order.

Removing the position property from the yellow container removes its stacking context and hence the browser gives precedence to the sticky header. That’s why the sticky header slides above the yellow container in the second case.

Stacking context with opacity property

In this example, we have two inputs, a select element with custom styles (just a mock, non-functional) and two checkboxes with some custom styles.

Toggle the select element in the demo now. You would notice that the checkbox overlaps with the dropdown when it is open. Despite having a z-index of 100000, the dropdown appears below the checkbox.

You may be wondering how opacity could determine what comes on top. That’s how the browser algorithm works. An element with an opacity of less than 1 creates a stacking context. It has lesser priority when compared to the element with a position value other than static.

Since the label element has position: relative, it has more precedence over the element with opacity property. That’s why the checkbox appears on top.

There are many other ways to create stacking context. Here is the complete list from the MDN docs.

How z-index works?

The z-index is used to specify the stacking order of an element. An element with greater stack order will appear at the front of the element with lower stack order.

While we all know this, sometimes we experience a weird behavior where even after specifying a larger z-index, the element won’t show up, giving us a lot of headaches, especially in large scale apps.

In this example, the red container is a child of the orange container whose z-index is 1.

So, naturally, the orange container will create a stacking context due to its z-index. All the children within this container are contained within this context.

The orange container with a z-index of 1 is stacked below the pink container with a z-index of 2.

Since the pink container has the higher z-index, all the children of the orange container are stacked below the pink container even if the children of the orange container have a z-index higher than 2.

The children of the orange container are stacked according to the same rules of stacking context within its own context. The child with z-index 88888 will be behind its sibling with z-index 99999.

If you remove the z-index property from the orange container, then the red and blue containers bump up. Since we have removed the z-index from the orange container, its stacking context no longer exists. The children elements with z-index 88888 and z-index 99999 act independently now by creating their own stacking context. They are placed in front of the pink container and are not bound by their parent container anymore.

z-index on flex and grid containers

Most people think z-index only works when the elements have a position property with a value other than static. But that is not the case.

z-index works on flex or grid children without the position property. Let’s look at an example.

We have three pricing cards that are children of a flex parent element .pricing. The standard plan card is moved up with a z-index of 1. This z-index works without specifying any position property.

You can create a stacking context on flex/grid child without position property.

z-index with isolation property

The below example has three pricing plan cards with a sticky header.

Try to scroll the container. Boom! 💥 The standard plan appears on top of the header.

You could solve the problem in one of the following ways.

  1. Add a z-index value to the header higher than the standard plan
  2. Add position: relative and a z-index value less than the header to the pricing list (.pricing class)
  3. Add isolation: isolate to pricing element ( .pricing class)

The first approach is straightforward and does not require further explanation.

The second approach is similar to one of the examples we have seen above. If an element has a z-index, it creates a stacking context and, the browser arranges all its children within this context. Specifying a z-index of 0 (less than the header’s z-index) to the .pricing class will produce the desired behavior.

The third approach, addingisolation: isolate property creates a new stacking context for the container and forces the elements within the container to be within this context without creating any unwanted side effect.

The first two approaches may create new problems in the future. The new z-index of the pricing list/header may conflict with some other value in the DOM.

We should think twice before adding a z-index value to any container. The value you add now may be compatible with the current DOM structure, but as the project gets complex, the z-index values will grow like a spider web and can get difficult to maintain. The more z-index values you add, the more problems you bring.

As we saw earlier, there are many ways to create a stacking context without specifying a z-index. We should try to use the one that fits best for the scenario.

The isolation: isolate property is the winner for this scenario. The standard price card won’t leak its z-index outside its parent. Also, adding this property does not create any unnecessary side effects to other elements in the document.

This property is also handy when positioning modals and dropdowns.

The example below has a #rootelement that contains all the elements in the app. The setup is similar to a React app.

Click the button to open the modal. The header appears on top when opening the modal.

We can fix this by adding isolation: isolate to #root. This creates a stacking context for #root and all the children are contained within this context. Since we append the modal to body(recommended especially for large apps), the modal render on top of other elements and #root elements children won’t leak their z-index outside the container anymore.

The final takeaway is

  • Stacking contexts can be contained in other stacking contexts, and together create a hierarchy of stacking contexts.
  • Each stacking context is completely independent of its siblings: only descendant elements are considered when stacking is processed.
  • Each stacking context is self-contained: after the element’s contents are stacked, the whole element is considered in the stacking order of the parent stacking context.

z-index is necessary in some scenarios to change the stacking order. But before you decide to go with z-index, make sure if there are other ways to solve the problem. Have a fixed set of z-index for the app and use them sparingly for good long-term maintenance.

I hope after reading this blog, you will feel much more comfortable in handling z-index.

Wish you a happy new year!

If you like my content, follow me at https://twitter.com/aishwarya2593

--

--