CSS z-index: Why a Huge Number Still Does Not Work
When a huge CSS z-index changes nothing, inspect the element’s stacking context before increasing the number. A child can sit above its siblings while its entire parent context remains behind another part of the page.
A value such as 999999 only competes at the level where it participates. It cannot promote every ancestor at once.
View code
<label>Sidebar z-index <select id="level"><option value="1">1 (behind main)</option><option value="3">3 (above main)</option><option value="auto">auto (no numbered context)</option></select></label>
<div class="stage"><aside class="sidebar">Sidebar<span class="tooltip">Tooltip: z-index 999999</span></aside><main class="main">Main panel: z-index 2</main></div>
<p class="note">The tooltip keeps the same large number. Change its parent to alter the overlap.</p>button, select { padding: .35rem .6rem; border: 1px solid var(--clg); background: var(--cw); color: var(--cb); }
input[type="range"] { width: min(100%, 18rem); vertical-align: middle; }
h2, h3 { font-size: 1rem; }
.note { color: var(--clg); }
select { max-width: 100%; }
.stage { position: relative; height: 10rem; margin-top: 1rem; isolation: isolate; }
.sidebar { position: absolute; inset: 0 auto 0 0; width: 40%; padding: .75rem; z-index: 1; background: var(--cg); }
.tooltip { position: absolute; top: 3rem; left: 60%; width: min(12rem, 60vw); padding: .75rem; z-index: 999999; border: 2px solid var(--cl); background: var(--cw); color: var(--cb); }
.main { position: absolute; inset: 0 0 0 40%; padding: .75rem; z-index: 2; background: var(--cb); color: var(--cw); }document.querySelector('#level').addEventListener('change', event => {
document.querySelector('.sidebar').style.zIndex = event.target.value;
});CSS z-index compares elements within a stacking context
Start with the ordinary overlap case:
.popover {
position: absolute;
z-index: 20;
}
.header {
position: sticky;
top: 0;
z-index: 10;
}
The popover can appear above the header when their contexts allow that comparison. The stacking context reference explains why the number alone cannot establish the final order.
The z-index property applies to positioned elements and also to flex and grid items. “Add position: relative everywhere” is not a complete model of how it works.
Elements at equal levels are resolved by the painting rules, which often makes later document content appear above earlier content. Avoid relying on that shorthand when an ancestor creates another context.
Reproduce the parent boundary with a small example
Use two sibling panels:
<div class="stage">
<aside class="sidebar">
Sidebar
<span class="sidebar__tooltip">A tooltip with a large z-index</span>
</aside>
<main class="main">Main panel</main>
</div>
.stage {
position: relative;
width: 20rem;
height: 10rem;
}
.sidebar {
position: absolute;
inset: 0 auto 0 0;
width: 8rem;
z-index: 1;
background: #dddddd;
color: #222222;
}
.sidebar__tooltip {
position: absolute;
top: 2rem;
left: 6rem;
width: 12rem;
padding: 0.5rem;
z-index: 999999;
background: #ffe08a;
color: #222222;
}
.main {
position: absolute;
inset: 0 0 0 8rem;
z-index: 2;
background: #225577;
color: #ffffff;
}
The tooltip extends into the main panel’s area. Its parent creates a context at level 1, while the main panel is at 2, so the tooltip remains behind the main panel there.
The fixed stage dimensions are for the demonstration. They make the overlap deliberate rather than depending on whatever layout surrounds the example.
Change the relationship that causes the overlap
For this exact example, raising the sidebar’s context changes the result:
.sidebar {
z-index: 3;
}
That raises the whole sidebar context, including its tooltip. It may be appropriate, but it is a broader decision than raising the tooltip alone.
Another option is removing an unnecessary context. Changing the sidebar’s z-index to auto removes the positioned element’s numbered context in this example, provided no other context-creating property remains.
For an application overlay, moving the overlay to a suitable container can be more deliberate than continually raising sections of the page. Choose the owner based on the interaction, not just on which selector is easiest to edit.
Look for the property that created the context
Common causes include a positioned element with a non-auto z-index, fixed or sticky positioning, reduced opacity, transforms, and filters. isolation: isolate can create a context deliberately.
Container-related properties and other rendering features can create contexts too. Use the reference’s current list rather than assuming only position and z-index matter.
Inspect the ancestors between the overlay and the root. A transform added for an animation can affect the layering of an otherwise unrelated tooltip inside it.
The isolation property can establish a boundary intentionally. An accidental boundary and a deliberate one deserve different fixes.
Separate clipping from paint order
An ancestor with overflow clipping can hide the part of a child outside its bounds. Raising z-index does not generally remove that clipping.
When the overlay is cut off exactly at a parent edge, inspect overflow and clipping properties before changing stack levels. When it remains visible but appears behind another panel, inspect the contexts and painting order.
Modal dialogs and shown popovers can participate in the browser’s top layer. They should not be modeled as ordinary elements with an even larger document z-index.
Use a small layer scale after fixing the structure
Named values make intent visible:
:root {
--layer-content: 0;
--layer-header: 10;
--layer-popover: 20;
--layer-modal: 30;
}
These are document-layer conventions, not a way to configure the browser’s top layer. They also do not let a child escape a parent context.
I prefer a small scale because it makes an unexpected value worth investigating. Fix the context or clipping boundary first; choosing the number should be the easy part.
Filed under CSS on
Last updated on