A large z-index cannot escape every ancestor
Stacking determines which boxes paint in front of others. A stacking context groups descendants into one unit in its parent's stacking order. Increasing a child's z-index cannot place it above a different ancestor context that is painted later.
.panel { position: relative; z-index: 1; }
.panel__popup { position: absolute; z-index: 9999; }
.overlay { position: fixed; inset: 0; z-index: 2; }If .panel and .overlay are siblings in the same parent stacking context, the popup remains within the panel's z-index:1 context. Its 9999 does not compete directly with the sibling overlay's 2.
What creates a context
Common triggers include a positioned element with a non-auto z-index, fixed or sticky positioning, opacity below 1, transforms and isolation:isolate. Flex and grid items also have relevant z-index behavior even without ordinary positioning. These are examples, not an exhaustive list.
Debug the ancestor chain
- Identify the element that is unexpectedly behind another.
- Inspect its ancestors for transforms, opacity, position and z-index.
- Find the nearest contexts that actually compete with each other.
- Adjust the correct layer or relocate the popup through the application's intended overlay mechanism.
Do not keep increasing numbers on the child without checking its context. Also inspect overflow clipping: a high stacking level does not automatically escape an ancestor's clipping region.
Use a small layer system
:root {
--layer-header: 10;
--layer-menu: 20;
--layer-dialog: 30;
}Named values document the intended order. They still work only within the actual context hierarchy. Native top-layer elements such as an opened dialog have their own behavior and should not be treated as ordinary descendants competing by an arbitrary z-index.
Practice
Create two positioned sibling panels. Put a high-z popup in the lower panel and verify that changing the panel's own stacking level changes the result. Then add overflow:hidden and distinguish clipping from stacking.