Two elements have a z-index of 9999 and 10, and everything suggests the first one will sit in front of the second. Yet on screen, the opposite happens. This kind of surprise is almost always the symptom of a stacking context: a subgroup of elements that stack among themselves, isolated from the rest of the page, in a way that z-index alone cannot escape.
Understanding this mechanism becomes essential as soon as an interface layers elements on top of each other: a menu above the content, a modal above everything, a tooltip that must stay visible. Without it, debugging an element "hidden behind another one" turns into a series of random guesses with ever larger z-index values, often with no effect at all.
Definition
A stacking context is a grouping of HTML elements whose display order relative to each other, along the axis that runs toward the screen or into the page, is decided independently from the rest of the document. Inside a context, z-index values compare normally against each other. But an entire context, together with all its children, stacks as a single block against elements outside it, as if it were one object.
What creates a new context
The document root, the html element, always creates a stacking context. Beyond that, several CSS declarations create a new one on whichever element carries them, even without asking for it explicitly.
| Declaration | Condition |
|---|---|
position: relative or absolute with z-index | z-index other than auto |
position: fixed or sticky | always, even without z-index |
| opacity | value below 1 |
| transform | value other than none |
filter, will-change, mix-blend-mode | value other than the initial one |
This list is not exhaustive, but it covers the vast majority of cases you will run into in practice. The common thread: these are almost always properties tied to animation or visual rendering, two areas where the browser benefits from isolating an element into its own rendering layer.
Why a high z-index can still lose
Here is the central trap: an element's z-index is only compared against other elements inside its own stacking context. If its parent has itself created a context, for instance with an opacity below 1, then the child's final position is first fixed by where the parent sits in the outer context, before the child's own z-index even comes into play.
.parent-a {
position: relative;
z-index: 1;
opacity: 0.99; /* creates a stacking context */
}
.parent-a .child {
position: relative;
z-index: 9999; /* huge, but trapped inside the parent's context */
}
.parent-b {
position: relative;
z-index: 2; /* sits in front of all of .parent-a, no matter its content */
}In this example, .child can never sit in front of .parent-b, whatever value its z-index has, because it is .parent-a as a whole, with a z-index of 1, that gets compared to .parent-b and its z-index of 2.
z-index that seems to do nothing, check whether one of its parents has an opacity below 1, a transform, or positioning combined with a z-index. That is almost always the real cause, not the z-index value itself.Stacking context is not layout
A stacking context should not be confused with a flexible or grid container, which organizes the position of elements within the plane of the page. A stacking context instead organizes their order along the depth axis, the one that decides what sits in front or behind. The two can trigger together on the same element, for instance a container with a z-index that also becomes a stacking context, but they are two independent mechanisms answering two different questions.
Diagnosing stacking that looks illogical
When an element stays stuck behind another despite a high z-index, the most reliable method is to walk up the tree of parents one by one, looking for whichever one carries an opacity, a transform, a filter, or positioning combined with a z-index. Modern browser developer tools often show stacking contexts directly in a three-dimensional inspection panel, which saves you from guessing.
FAQ
No, they are essential and often created on purpose, for example to guarantee that a modal always sits above everything else. The problem is not their existence, but not knowing where they sit within the page.
Yes, several properties create a context without any positioning at all, notably an opacity below 1, a transform, or a filter: positioning combined with z-index is only one case among others.
z-index take an element out of its stacking context?No, a negative z-index only places the element lower in the stacking order of its own context, often behind its parent's background, but it remains trapped inside that same context like any other value.