4 reasons your z-index isn‘t working (and how to fix it)

The z-index property in CSS seems simple at first glance. It controls the stacking order of elements on a webpage, allowing you to place one element on top of another. Elements with a higher z-index value appear in front of elements with a lower z-index value. Easy, right?

Unfortunately, z-index is trickier than it seems. Even experienced developers frequently run into stacking issues where their z-index doesn‘t work as expected. But don‘t worry – once you understand a few key concepts about how z-index and stacking contexts work, you‘ll be able to solve any z-index headaches that come your way!

In this in-depth guide, we‘ll break down the four most common reasons your z-index isn‘t behaving, and exactly how you can fix each scenario with sample code. If you‘ve ever been confused about z-index, this post will set you straight. Let‘s dive in!

Reason 1: Natural stacking order

Here‘s the first thing to understand about stacking order – even if you don‘t apply any z-index values, elements are still "stacked" in a particular order on the page. There‘s a natural hierarchy based on the order of appearance in your HTML.

By default, elements that come later in the markup will appear on top of earlier elements. So in this example, the two cat images are in their natural stacking order, with the second cat on top:

<div class="cat-1"></div>
<div class="cat-2"></div>

.cat-1, .cat-2 {
position: absolute; width: 200px; }

.cat-1 { top: 40px; left: 50px;
}

.cat-2 { top: 80px;
left: 100px; }

Two overlapping cat images, with cat 2 on top

But what if you want to flip the natural order and put the first cat on top? Simply set a z-index value on the first cat:

.cat-1 {
  z-index: 1;
}  

Cat 1 now on top after applying z-index

Easy fix! Setting a z-index value on an element moves it out of the natural stacking order. A z-index of 1 is enough to move the first cat to the top, since the second cat doesn‘t have any z-index set.

Key takeaway: Elements have a default stacking order even without z-index. The natural order puts elements that come later in the HTML on top of earlier elements.

Reason 2: Forgetting to set position

Setting a z-index value on an element will only work if that element‘s position property is set to something other than the default of static. This is the most common cause of z-index issues I see!

Here‘s an example. Let‘s say you have a dialog box that you want to appear on top of a background overlay:

<div class="overlay"></div>
<div class="dialog">
  <h3>Warning!</h3>
  <p>Delete all your files?</p>  
  <button>OK</button>
  <button>Cancel</button>
</div>

.overlay { position: fixed;
z-index: 5; background-color: rgba(0,0,0,.25); }

.dialog {
width: 300px; border-radius: 8px; background-color: white; z-index: 6; }

Even though the dialog has a higher z-index value than the overlay (6 vs 5), it still shows up underneath the overlay:

Dialog is hidden under the overlay

Why? Because we didn‘t set a position property on the dialog element! z-index only works on elements that have their position set to relative, absolute, or fixed. Let‘s fix it by adding position: fixed to the dialog:

 
.dialog {
  position: fixed;
  z-index: 6;
  width: 300px;  
  border-radius: 8px;
  background-color: white;   
}

And now the dialog pops to the top, thanks to its higher z-index:

Dialog now appears on top of overlay after setting position fixed

I find it helpful to think of z-index as the 3rd dimension – elements with a z-index are "popping out" of the flat webpage into a 3D space. But only positioned elements can enter this 3D space. Non-positioned elements are stuck in flatland, so they can‘t be moved forward or backward with z-index.

Key takeaway: For z-index to work on an element, you must set its position property to something other than static (the default). Use position: relative for elements in the normal document flow, or position: absolute or fixed for elements you want to remove from the flow.

Reason 3: Creating new stacking contexts

Stacking contexts are groups of elements with a common parent that move forward or backward together in the z-index. New stacking contexts can be formed in a few ways:

  • When an element is the root element of a document (like the <html> element)
  • When an element has a position value other than static and a z-index value other than auto
  • When an element has an opacity value less than 1
  • When an element has certain transform, filter, or perspective values set

When a new stacking context is formed, it essentially creates a "sub-group" of elements that have their own z-index hierarchy. Elements inside this group only compete with each other, not with elements outside the group.

This gets tricky when you have multiple levels of stacking contexts that are nested inside each other. Consider this example of an image gallery:

<div class="gallery">
  <figure class="featured">
    <img src="monkey.jpg">  
    <figcaption>A monkey!</figcaption>
  </figure>

<figure>
<img src="tiger.jpg"> </figure>

<figure> <img src="bear.jpg">
</figure>
</div>

.gallery { position: relative; z-index: 1; }

.featured { position: relative;
z-index: 2; }

figcaption { position: absolute; bottom: 0;
z-index: 3; background: rgba(0, 0, 0, .5); color: white; }

Let‘s break down the stacking contexts here:

  1. First, the gallery itself forms a new stacking context because it has a position and z-index set.

  2. Inside the gallery, the featured image also creates a new nested stacking context, again because of its position and z-index values.

  3. Then, inside the featured image, the figcaption forms yet another stacking context because of its position and z-index.

The key thing to understand is that the z-index values of the figcaption and featured image are relative to each other, but not to the gallery. No matter how high you set the z-index of the figcaption, it will never appear on top of any other images in the gallery, because it‘s stuck inside the featured image‘s stacking context.

Figcaption is limited by the featured image stacking context

Think of it like Russian nesting dolls – each stacking context is "nested" inside the one above it. A child element can‘t jump outside of its parent‘s stacking context.

To allow the figcaption to move freely on top of all images in the gallery, you‘d need to remove the stacking context created by the featured image – either by removing its position property or making its z-index auto.

.featured {
  position: relative;
  z-index: auto;  
}  

Figcaption can now move on top of all images after removing stacking context

Key takeaway: Certain CSS properties can create new stacking contexts, forming "sub-groups" of elements that only compete with each other. Be mindful of the hierarchy of stacking contexts in your code. If an element isn‘t moving as you expect, it may be trapped inside a parent stacking context.

Reason 4: Mixing different position values

When you‘re using z-index to stack multiple elements on top of each other, be careful about mixing different position values. While it‘s fine to use position: relative on some elements and position: absolute on others, things get complicated when you throw position: fixed into the mix.

Fixed positioning takes an element completely out of the document flow and positions it relative to the viewport. This is handy for things like persistent navigation bars that stay put while the page scrolls. But it can cause confusing stacking problems.

Here‘s a simple example. Say you‘ve got a full-screen fixed overlay with some text content sitting on top:

<div class="overlay"></div>  
<section class="content">
  <p>Pellentesque habitant morbi tristique...</p>
</section>

.overlay { position: fixed; z-index: 1;
background-color: rgba(0, 0, 0, .5); }

.content {
position: relative; z-index: 2; background-color: white; padding: 20px; margin: 20px auto; width: 600px; }

You‘d expect the content to appear on top of the overlay, since it has a higher z-index, right? But in reality, the overlay still covers the content!

Fixed overlay hides the relative positioned content

The problem is that we‘re dealing with two separate stacking contexts here. The fixed overlay exists in the root stacking context, on the same level as the <html> element. Meanwhile, the relatively positioned content exists in the stacking context of the page body.

No matter how high you set the z-index of the content, it will never appear on top of the fixed overlay. It‘s like the two elements are in separate, parallel dimensions – they don‘t acknowledge each other‘s z-index at all.

To resolve this, make sure all the elements you want to stack are using the same positioning scheme. Either make them all position: fixed (if you want them to stay in place on scroll), or make them all position: absolute or relative.

Here‘s the fixed version:

.overlay {
  position: fixed;
  z-index: 1;
  background-color: rgba(0, 0, 0, .5);  
}

.content { position: fixed;
z-index: 2; background-color: white; padding: 20px; width: 600px; top: 50%; left: 50%; transform: translate(-50%, -50%);
}

When both elements are fixed, content appears on top

And here‘s the absolute version (assumes the parent element has a position set):

.overlay {  
  position: absolute;
  z-index: 1;
  background-color: rgba(0, 0, 0, .5);
}

.content { position: absolute; z-index: 2;
background-color: white; padding: 20px; width: 600px; top: 50%; left: 50%; transform: translate(-50%, -50%); }

Key takeaway: When stacking multiple elements with z-index, avoid mixing position: fixed with position: relative or absolute. Fixed elements live in a separate stacking context and won‘t acknowledge the z-index of relatively or absolutely positioned elements.

To recap…

We covered a lot of ground in this post! To keep things straight, here‘s a quick summary of the four key reasons your z-index might not be working as expected:

  1. Elements have a default stacking order based on their order in the HTML. Elements that come later will appear on top of earlier ones.

  2. For z-index to work, an element must have its position property set to something other than the default static.

  3. Some CSS properties like opacity and transform create new stacking contexts, forming "sub-groups" of elements with their own z-index hierarchy.

  4. Mixing position: fixed with other positioning values can cause unexpected stacking issues.

Armed with this knowledge, you should be able to solve any z-index woes that come your way! Just remember to:

  • Always set a position value before using z-index
  • Be mindful of the stacking context hierarchy
  • Avoid mixing fixed with relative/absolute positioning

If you keep these guidelines in mind, z-index will become one of your most powerful CSS allies. Now go forth and stack elements with confidence!

Similar Posts