Skip to content
Laviyx Learn

Try

CSS course menu

CSS z-index and Stacking Contexts: Why z-index Is Not Working

Understand how CSS decides which element appears on top: painting order, z-index on positioned elements and flex and grid items, stacking contexts created by opacity, transform and isolation, and how to debug z-index problems.

Intermediate5 min readUpdated
On this page

When elements overlap, something has to be drawn on top. z-index controls that order along an imaginary z-axis that points out of the screen towards you.

The default order#

Without z-index, positioned elements are painted after (on top of) non-positioned ones, and later elements in the HTML are painted on top of earlier ones:

CSS
.box { position: absolute; width: 110px; height: 80px; padding: 6px; color: white; font: 14px sans-serif; }
.a { background: #2f5fe0; top: 10px; left: 10px; }
.b { background: #e8590c; top: 40px; left: 70px; }
.c { background: #2f9e44; top: 70px; left: 130px; }
HTML
<div class="box a">A (first)</div>
<div class="box b">B</div>
<div class="box c">C (last)</div>
Result

Using z-index#

z-index takes a whole number; higher numbers are drawn on top. It works on positioned elements (anything but static) and on flex and grid items:

CSS
.box { position: absolute; width: 110px; height: 80px; padding: 6px; color: white; font: 14px sans-serif; }
.a { background: #2f5fe0; top: 10px; left: 10px; z-index: 3; }
.b { background: #e8590c; top: 40px; left: 70px; z-index: 2; }
.c { background: #2f9e44; top: 70px; left: 130px; z-index: 1; }
HTML
<div class="box a">A z-index: 3</div>
<div class="box b">B z-index: 2</div>
<div class="box c">C z-index: 1</div>
Result

Now A is on top. Negative values are allowed and put an element behind its siblings.

Stacking contexts: why z-index "does not work"#

A stacking context is a group whose children are layered inside it. The whole group is then layered as one unit with its siblings. A child can never escape its parent's stacking context, no matter how large its z-index is.

CSS
.panel { position: relative; width: 220px; height: 100px; padding: 8px; color: white; font: 14px sans-serif; }
.one { background: #2f5fe0; z-index: 1; }
.two { background: #e8590c; z-index: 2; margin-top: -60px; margin-left: 60px; }
.tooltip {
  position: absolute;
  z-index: 9999;
  top: 50px;
  left: 20px;
  background: black;
  padding: 6px;
}
HTML
<div class="panel one">Panel one (z-index 1)
  <div class="tooltip">Tooltip, z-index 9999</div>
</div>
<div class="panel two">Panel two (z-index 2)</div>
Result

The tooltip has z-index: 9999, yet panel two covers it. The tooltip lives inside panel one's stacking context, and panel one (1) is below panel two (2).

What creates a stacking context#

Many properties create one, including:

  • position: relative or absolute with a z-index other than auto;
  • position: fixed or sticky;
  • opacity less than 1;
  • transform, filter, backdrop-filter, perspective, clip-path or mask with a value other than none;
  • a flex or grid item with a z-index;
  • isolation: isolate;
  • will-change naming one of the properties above.

This is why adding opacity: 0.99 or a transform to a parent can suddenly change what appears on top.

isolation: isolate#

isolation: isolate creates a stacking context without any visual side effects. Use it on components so their internal z-index values cannot leak out and fight with the rest of the page:

CSS
.card { isolation: isolate; }
.card .decoration { position: absolute; z-index: -1; }  /* stays behind the card's content, not the whole page */

Debugging z-index#

  1. Check the element is positioned (or a flex/grid item); otherwise z-index is ignored.
  2. Walk up its ancestors and look for anything that creates a stacking context.
  3. Compare the elements at the same level of stacking contexts, not the deeply nested ones.

Browsers' developer tools can help: Edge and Chrome have a 3D View / Layers panel that shows stacking visually.

Keep a small scale of values for a project, for example 1 for raised content, 10 for dropdowns, 100 for sticky headers and 1000 for modals, instead of ever-growing numbers.

Practice#

Practice exercise

Fix the example above so the tooltip appears above panel two, without changing the HTML.

Show solution
CSS
.panel { position: relative; width: 220px; height: 100px; padding: 8px; color: white; font: 14px sans-serif; }
.one { background: #2f5fe0; z-index: 3; }
.two { background: #e8590c; z-index: 2; margin-top: -60px; margin-left: 60px; }
.tooltip { position: absolute; z-index: 1; top: 50px; left: 20px; background: black; padding: 6px; }
HTML
<div class="panel one">Panel one
  <div class="tooltip">Tooltip, now on top</div>
</div>
<div class="panel two">Panel two</div>
Result

The tooltip can only rise as high as its parent's stacking context, so the fix is to raise panel one above panel two. (Another option is to remove z-index from both panels, so they no longer create stacking contexts.)

Key takeaway#

Finished this lesson?