č§£åÆ CSS å å äøäøę
Source: Smashing Magazine
Have you ever set z-index: 99999 on an element in your CSS, and it doesnāt come out on top of other elements? A value that large should easily place that element visually on top of anything else, assuming all the different elements are set at either a lower value or not set at all.
A webpage is usually represented in a two-dimensional space; however, by applying specific CSS properties, an imaginary z-axis plane is introduced to convey depth. This plane is perpendicular to the screen, and from it, the user perceives the order of elements, one on top of the other. The idea behind the imaginary z-axis, the userās perception of stacked elements, is that the CSS properties that create it combine to form what we call a stacking context.
Weāre going to talk about how elements are āstackedā on a webpage, what controls the stacking order, and practical approaches to āunstackā elements when needed.
About Stacking ContextsImagine your webpage as a desk. As you add HTML elements, youāre laying pieces of paper, one after the other, on the desk. The last piece of paper placed is equivalent to the most recently added HTML element, and it sits on top of all the other papers placed before it. This is the normal document flow, even for nested elements. The desk itself represents the root stacking context, formed by the <html> element, which contains all other folders.
Now, specific CSS properties come into play.
Properties like position (with z-index), opacity, transform, and contain) act like a folder. This folder takes an element and all of its children, extracts them from the main stack, and groups them into a separate sub-stack, creating what we call a stacking context. For positioned elements, this happens when we declare a z-index value other than auto. For properties like opacity, transform, and filter, the stacking context is created automatically when specific values are applied.

Try to understand this: Once a piece of paper (i.e., a child element) is inside a folder (i.e., the parentās stacking context), it can never exit that folder or be placed between papers in a different folder. Its z-index is now only relevant inside its own folder.
In the illustration below, Paper B is now within the stacking context of Folder B, and can only be ordered with other papers in the folder.

Imagine, if you will, that you have two folders on your desk:
<div class="folder-a">Folder A</div>
<div class="folder-b">Folder B</div>
.folder-a { z-index: 1; }
.folder-b { z-index: 2; }
Letās update the markup a bit. Inside Folder A is a special page, z-index: 9999. Inside Folder B is a plain page, z-index: 5.
<div class="folder-a">
<div class="special-page">Special Page</div>
</div>
<div class="folder-b">
<div class="plain-page">Plain Page</div>
</div>
.special-page { z-index: 9999; }
.plain-page { z-index: 5; }
Which page is on top?
Itās the .plain-page in Folder B. The browser ignores the child papers and stacks the two folders first. It sees Folder B (z-index: 2) and places it on top of Folder A (z-index: 1) because we know that two is greater than one. Meanwhile, the .special-page set to z-index: 9999 page is at the bottom of the stack even though its z-index is set to the highest possible value.
Stacking contexts can also be nested (folders inside folders), creating a āfamily tree.ā The same principle applies: a child can never escape its parentsā folder.
Now that you get how stacking contexts behave like folders that group and reorder layers, itās worth asking: why do certain properties ā like transform and opacity ā create new stacking contexts?
Hereās the thing: these properties donāt create stacking contexts because of how they look; they do it because of how the browser works under the hood. When you apply transform, opacity, filter, or perspective, youāre telling the browser, āHey, this element might move, rotate, or fade, so be ready!ā

When you use these properties, the browser creates a new stacking context to manage rendering more efficiently. This allows the browser to handle animations, transforms, and visual effects independently, reducing the need to recalculate how these elements interact with the rest of the page. Think of it as the browser saying, āIāll handle this folder separately so I donāt have to reshuffle the entire desk every time something inside it changes.ā
But thereās a side effect. Once the browser lifts an element into its own layer, it must āflattenā everything within it, creating a new stacking context. Itās like taking a folder off the desk to handle it separately; everything inside that folder gets grouped, and the browser now treats it as a single unit when deciding what sits on top of what.
So even though the transform and opacity properties might not appear to affect the way that elements stack visually, they do, and itās for performance optimisation. Several other CSS properties can also create stacking contexts for similar reasons. MDN provides a complete list if you want to dig deeper. There are quite a few, which only illustrates how easy it is to inadvertently create a stacking context without knowing it.
Stacking issues can arise for many reasons, but some are more common than others. Modal components are a classic pattern because they require toggling the component to āopenā on a top layer above all other elements, then removing it from the top layer when it is āclosed.ā
Iām pretty confident that all of us have run into a situation where we open a modal and, for whatever reason, it doesnāt appear. Itās not that it didnāt open properly, but that it is out of view in a lower layer of the stacking context.
This leaves you to wonder āhow come?ā since you set:
.overlay {
position: fixed; /* creates the stacking context */
z-index: 1; /* puts the element on a layer above everything else */
inset: 0;
width: 100%;
height: 100vh;
overflow: hidden;
background-color: #00000080;
}
This looks correct, but if the parent element containing the modal trigger is a child element within another parent element thatās also set to z-index: 1, that technically places the modal in a sublayer obscured by the main folder. Letās look at that specific scenario and a couple of other common stacking-context pitfalls. I think youāll see not only how easy it is to inadvertently create stacking contexts, but also how to mismanage them. Also, how you return to a managed state depends on the situation.
Scenario 1: The Trapped Modal

You can immediately see your modal trapped in a low-level layer and identify the parent.
Browser Extensions
Smart developers have built extensions to help. Tools like this āCSS Stacking Context Inspectorā Chrome extension add an extra z-index tab to your DevTools to show you information about elements that create a stacking context.

IDE Extensions
You can even spot issues during development with an extension like this one for VS Code, which highlights potential stacking context issues directly in your editor.

After weāve identified the root cause, the next step is to deal with it. There are several approaches you can take to tackle this problem, and Iāll list them in order. You can choose anyone at any level, though; no one can complain or obstruct another.
Change The HTML Structure
This is considered the optimal fix. For you to run into a stacking context issue, you must have placed some elements in funny positions within your HTML. Restructuring the page will help you reshape the DOM and eliminate the stacking context problem. Find the problematic element and remove it from the trapping element in the HTML markup. For instance, we can solve the first scenario, āThe Trapped Modal,ā by moving the .modal-container out of the header and placing it in the <body> element by itself.
<header class="header">
<h2>Header</h2>
<button id="open-modal">Open Modal</button>
<!-- Former position -->
</header>
<main class="content">
<h1>Main Content</h1>
<p>This content has a z-index of 2 and will still not cover the modal.</p>
</main>
<!-- New position -->
<div id="modal-container" class="modal-container">
<div class="modal-overlay"></div>
<div class="modal-content">
<h3>Modal Title</h3>
<p>Now, I'm not behind anything. I've gotten a better position as a result of DOM restructuring.</p>
<button id="close-modal">Close</button>
</div>
</div>
When you click the āOpen Modalā button, the modal is positioned in front of everything else as itās supposed to be.
See the Pen Scenario 1: The Trapped Modal (Solution) [forked] by Shoyombo Gabriel Ayomide.
Adjust The Parent Stacking Context In CSS
What if the element is one you canāt move without breaking the layout? Itās better to address the issue: the parent establishes the context. Find the CSS property (or properties) responsible for triggering the context and remove it. If it has a purpose and cannot be removed, give the parent a higher z-index value than its sibling elements to lift the entire container. With a higher z-index value, the parent container moves to the top, and its children appear closer to the user.
Based on what we learned in āThe Submerged Dropdownā scenario, we canāt move the dropdown out of the navbar; it wouldnāt make sense. However, we can increase the z-index value of the .navbar container to be greater than the .content elementās z-index value.
.navbar {
background: #333;
/* z-index: 1; */
z-index: 3;
position: relative;
}
With this change, the .dropdown-menu now appears in front of the content without any issue.
See the Pen Scenario 2: The Submerged Dropdown (Solution) [forked] by Shoyombo Gabriel Ayomide.
Try Portals, If Using A Framework
In frameworks like React or Vue, a Portal is a feature that lets you render a component outside its normal parent hierarchy in the DOM. Portals are like a teleportation device for your components. They let you render a componentās HTML anywhere in the document (typically right into document.body) while keeping it logically connected to its original parent for props, state, and events. This is perfect for escaping stacking context traps since the rendered output literally appears outside the problematic parent container.
ReactDOM.createPortal(
<ToolTip />,
document.body
);
This ensures your dropdown content isnāt hidden behind its parent, even if the parent has overflow: hidden or a lower z-index.
In the āThe Clipped Tooltipā scenario we looked at earlier, I used a Portal to rescue the tooltip from the overflow: hidden clip by placing it in the document body and positioning it above the trigger within the container.
See the Pen Scenario 3: The Clipped Tooltip (Solution) [forked] by Shoyombo Gabriel Ayomide.
Introducing Stacking Context Without Side EffectsAll the approaches explained in the previous section are aimed at āunstackingā elements from problematic stacking contexts, but there are some situations where youāll actually need or want to create a stacking context.
Creating a new stacking context is easy, but all approaches come with a side effect. That is, except for using isolation: isolate. When applied to an element, the stacking context of that elementās children is determined relative to each child and within that context, rather than being influenced by elements outside of it. A classic example is assigning that element a negative value, such as z-index: -1.
Imagine you have a .card component. You want to add a decorative shape that sits behind the .cardās text, but on top of the cardās background. Without a stacking context on the card, z-index: -1 sends the shape to the bottom of the root stacking context (the whole page). This makes it disappear behind the .cardās white background:
See the Pen Negative z-index (problem) [forked] by Shoyombo Gabriel Ayomide.
To solve this, we declare isolation: isolate on the parent .card:
See the Pen Negative z-index (solution) [forked] by Shoyombo Gabriel Ayomide.
Now, the .card element itself becomes a stacking context. When its child element ā the decorative shape created on the :before pseudo-element ā has z-index: -1, it goes to the very bottom of the parentās stacking context. It sits perfectly behind the text and on top of the cardās background, as intended.
Remember: the next time your z-index seems out of control, itās a trapped stacking context.
References
- Stacking context (MDN)
- Z-index and stacking contexts (web.dev)
- āHow to Create a New Stacking Context with the Isolation Property in CSSā, Natalie Pina
- āWhat The Heck, z-index??ā, Josh Comeau
Further Reading On SmashingMag
- āManaging CSS Z-Index In Large Projectsā, Steven Frieson
- āSticky Headers And Full-Height Elements: A Tricky Combinationā, Philip Braunen
- āManaging Z-Index In A Component-Based Web Applicationā, Pavel Pomerantsev
- āThe Z-Index CSS Property: A Comprehensive Lookā, Louis Lazaris
