Skip to content
AgileLogics
5 min read

Stacking and z-index

Prerequisites: Positioning

When elements overlap, the stacking order decides which is on top.

  • By default, later elements in the HTML paint over earlier ones, and positioned elements paint over non-positioned ones.
  • z-index changes the order of positioned elements (and flex and grid items). Higher numbers are closer to you.

Stacking contexts

The surprise: z-index only compares elements within the same stacking context. Some properties create a new context, and everything inside it is stacked as one unit relative to the outside:

  • position other than static with a z-index other than auto
  • position: fixed or sticky
  • opacity less than 1
  • transform, filter, backdrop-filter, perspective
  • isolation: isolate
  • will-change naming one of those properties
  • flex and grid items with a z-index
<style>
  .a { position: relative; z-index: 1; }
  .a .tooltip { position: absolute; z-index: 9999; }
  .b { position: relative; z-index: 2; }
</style>
<div class="a"><div class="tooltip">Tooltip</div></div>
<div class="b">Header</div>
<script>
  const tip = document.querySelector(".tooltip");
  const b = document.querySelector(".b");
  tip.style.top = "0";
  const r = b.getBoundingClientRect();
  tip.style.top = (r.top - tip.parentElement.getBoundingClientRect().top) + "px";
  const topEl = document.elementFromPoint(r.left + 5, r.top + 5);
  console.log("on top:", topEl.className);
</script>
on top: b

The tooltip has z-index: 9999 but still sits under .b: it lives inside .a's stacking context, and .a (1) is below .b (2). No z-index inside .a can escape that.

Practical advice

  • Keep z-index values in a small, documented scale (for example, custom properties --z-dropdown: 10, --z-sticky: 20, --z-modal: 100) — not escalating 9999s.
  • Use isolation: isolate on a component to contain its internal z-indexes without changing its own position.
  • When z-index "doesn't work", look for an ancestor that creates a stacking context.
  • The top layer — used by dialog with showModal(), popovers, and full screen — sits above every stacking context, which is why these features solve most overlay problems without z-index.