How does z-index work in CSS?

z-index controls stacking order, but only works on positioned elements inside the same stacking context.

CSSLayout

Overview

z-index sets which element appears in front when elements overlap. Higher values are in front. It sounds simple, but stacking contexts make it surprising: a z-index of 9999 inside a context with a low z-index will still sit behind elements outside that context.

Key Points

  • •z-index only works on elements with `position` set to anything other than `static`.
  • •A new stacking context is created by: `position` + `z-index`, `opacity < 1`, `transform`, `filter`, `isolation: isolate`, and several others.
  • •Elements within a stacking context are painted together their z-indices are compared only within that context.
  • •`z-index: auto` (default) does not create a new stacking context.
  • •Use `isolation: isolate` on a container to prevent internal z-index from bleeding out.

Examples

Basic z-index stacking

css
.behind  { position: relative; z-index: 1; }
.infront { position: relative; z-index: 2; } /* renders on top */

The stacking context trap

css
/* Parent creates a stacking context at z-index: 1 */
.modal-backdrop {
  position: fixed;
  z-index: 1;
  opacity: 0.5; /* <-- this alone creates a stacking context */
}

/* Even z-index: 9999 inside .modal-backdrop can't escape z-index: 1 */
.modal-backdrop .close-btn {
  position: relative;
  z-index: 9999; /* only beats other elements INSIDE .modal-backdrop */
}

opacity < 1 always creates a stacking context a common source of confusion.

Use isolation: isolate to contain a component

css
.dropdown-component {
  isolation: isolate; /* internal z-index stays inside this component */
}

Pro Tip

When z-index isn't working, open DevTools and check whether a parent element has `opacity`, `transform`, or `filter` these create a stacking context and contain z-index. Adding `isolation: isolate` to the container is usually the cleanest fix.