Menu

z-index in CSS: Stacking Order and Stacking Contexts

z-index decides which overlapping element is drawn on top. Learn which elements it works on, how stacking contexts group elements so that z-index 9999 can still lose, what creates a stacking context, and how to fix z-index that does not work.

This page includes runnable editors - edit, run, and see output instantly.

What z-index does

When elements overlap, z-index decides which one is drawn in front. A higher number wins. It works on positioned elements (any position except static) and on flex and grid items:

Without z-index, later elements in the HTML are drawn on top of earlier ones, so C would cover B and B would cover A. The z-index values reverse that: A is in front, then C, then B.

The values

ValueMeaning
auto (default)Stacked at level 0 with its siblings, and creates no stacking context
0Level 0, but creates a stacking context
Positive integerIn front of lower values
Negative integerBehind level 0, and behind a parent's content if the parent forms the stacking context

Only integers are valid: z-index: 1.5 is ignored.

Why z-index 9999 can lose: stacking contexts

z-index values are not compared across the whole page. Elements are grouped into stacking contexts, and a z-index only orders elements inside the same context. The context itself is then placed as a single layer, using the z-index of the element that created it.

Below, the blue dropdown has z-index: 9999, but it lives inside a header with z-index: 1. The green banner, a sibling of that header, has z-index: 2. The whole header layer, dropdown included, is below the banner:

Press the button. With z-index: auto the header no longer forms a stacking context, so the dropdown's 9999 is compared with the banner's 2 directly, and the dropdown comes to the front. Raising the dropdown to 99999 would never have helped.

What creates a stacking context

Any of these turns an element into its own stacking context, trapping the z-index of everything inside it:

PropertyCondition
position: relative or absoluteWith a z-index other than auto
position: fixed or stickyAlways
Flex or grid itemWith a z-index other than auto
opacityBelow 1
transform, scale, rotate, translateAny value other than none
filter, backdrop-filterAny value other than none
isolationisolate
mix-blend-modeAny value other than normal
will-changeNaming any of the properties above
containlayout or paint (and strict, content)

The surprising ones are opacity and transform: adding a fade or a hover animation to a parent can suddenly put its dropdown behind the next section.

The tooltip's z-index: 10 should put it above the next column. But opacity: 0.99 makes the first column a stacking context with z-index auto (level 0), and the second column comes later in the HTML at the same level, so it is painted over the whole first column, tooltip included. Delete the opacity line and the tooltip is in front again.

Negative z-index

A negative z-index puts an element behind its siblings. If the parent does not form a stacking context, it can even go behind the parent's background, which is a common surprise. isolation: isolate on the parent creates a context without any other visual effect and keeps the negative child inside it:

Without isolation, the amber frame drops behind the button's own gray background, so only its edge shows. With isolation: isolate, the frame stays inside the button's stacking context: above its background, below its text.

A z-index scale

Pick a few named layers for the whole site instead of escalating numbers:

:root {
  --z-dropdown: 10;
  --z-sticky: 20;
  --z-overlay: 30;
  --z-modal: 40;
  --z-toast: 50;
}
.modal { position: fixed; z-index: var(--z-modal); }

Most layering bugs are stacking-context bugs, not number bugs, so a small scale plus isolation: isolate on components that use z-index internally goes a long way. z-index needs positioning to work; the position page covers that.

Common mistakes

  • z-index on a static element. Add position: relative (or make it a flex or grid item).
  • Escalating to 9999. If a parent forms a stacking context below the competitor, no child value can win. Find the context instead.
  • A new opacity, transform or filter on a parent that suddenly traps its dropdown.
  • Decimal values. z-index: 1.5 is invalid.
  • Expecting z-index: 0 and auto to behave the same. 0 creates a stacking context; auto does not.
  • Modals inside deeply nested components. Render them near the end of <body> (or use the <dialog> element's top layer), so no ancestor context can trap them.

Frequently Asked Questions

What does z-index do?

It sets the stacking order of overlapping elements: a higher value is drawn in front of a lower one within the same stacking context. The default is auto, and values are whole numbers, including negative ones.

Why is my z-index not working?

Either the element is not positioned (z-index needs position other than static, or a flex or grid child), or it sits inside a parent that forms a stacking context with a lower order. A child can never rise above an element that its stacking context is placed below, whatever its own z-index.

What is a stacking context?

A group of elements that are stacked together as one layer. Inside it, children are ordered by their z-index; outside, the whole group is placed by the z-index of the element that created it.

What creates a stacking context?

Among others: the root element, a positioned element with a z-index other than auto, position: fixed or sticky, a flex or grid child with a z-index, opacity below 1, transform, filter, isolation: isolate, and will-change on those properties.

Does z-index work without position?

Not on normal block or inline elements: position: static ignores it. It does work on flex items and grid items without any position.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED