How do z-index and stacking contexts work?

CSS

The short answer

z-index controls the vertical stacking order of elements — higher values appear in front of lower values. But it only works on positioned elements (position: relative, absolute, fixed, or sticky). The tricky part is stacking contexts — a new stacking context creates a self-contained layer where z-index values only compete within that context, not globally.

z-index basics

.box-a {
position: relative;
z-index: 1;
}
.box-b {
position: relative;
z-index: 2;
}
/* box-b appears in front of box-a */

z-index only works on elements with position set to something other than static (the default). Without position, z-index does nothing.

What creates a stacking context

This is where most confusion comes from. A new stacking context is created by:

  • position: relative/absolute/fixed with z-index set (not auto)
  • opacity less than 1
  • transform, filter, perspective, clip-path
  • position: fixed or position: sticky
  • isolation: isolate
  • Flex/grid children with z-index set

Why stacking contexts matter

Elements inside a stacking context can only compete with each other — they cannot "escape" their parent's context.

.parent-a {
position: relative;
z-index: 1;
}
.child-a {
position: relative;
z-index: 9999;
}
.parent-b {
position: relative;
z-index: 2;
}
.child-b {
position: relative;
z-index: 1;
}

Even though .child-a has z-index: 9999, it still appears behind .child-b because .parent-a (z-index: 1) is behind .parent-b (z-index: 2). The child cannot escape its parent's stacking context.

This is the most common reason "z-index is not working" — the element's parent has a lower stacking context than the element it is trying to appear in front of.

The default stacking order

Without any z-index, elements stack in this order (back to front):

  1. Background and borders of the root element
  2. Non-positioned block elements in document order
  3. Floated elements
  4. Inline elements
  5. Positioned elements in document order

Debugging z-index issues

When z-index is not working:

  1. Check if the element has position set (not static)
  2. Check if a parent creates a stacking context (look for opacity, transform, z-index on ancestors)
  3. Use browser DevTools — some browsers show stacking contexts in the Elements panel

isolation: isolate

If you want to create a stacking context without side effects:

.component {
isolation: isolate;
}

This creates a new stacking context without changing position, opacity, or transform. It is useful in component libraries where you want to prevent z-index leaking between components.

Common Pitfalls

A very common CSS pitfall is setting z-index: 9999 and wondering why it still does not work. The problem is almost always a parent element creating a stacking context with a lower z-index. No matter how high you set the child's z-index, it cannot break out of its parent's context. Always check the parent hierarchy when debugging z-index issues.

Interview Tip

The key concept interviewers want to hear is stacking contexts — not just z-index values. Show the parent-child example where a child with high z-index is still behind another element because of its parent's context. If you can mention isolation: isolate, that shows advanced CSS knowledge.

Why interviewers ask this

z-index problems are some of the most frustrating CSS issues. Interviewers ask about it to see if you understand stacking contexts, not just the z-index property itself. A candidate who can explain why "z-index 9999 does not work" demonstrates deep CSS understanding.