Z-Index and Stacking Context in CSS

What You Learn From This Page

  • How z-index controls local stacking levels rather than global layer scores.
  • How stacking contexts group descendants and participate atomically in parent contexts.
  • Which positioning, layout, visual-effect, containment, and compositing properties create contexts.
  • How auto, zero, positive, negative, and equal stack levels differ.
  • Why clipping, containing blocks, top-layer UI, and 3D transforms are separate concerns.
  • How to design maintainable local and global layer systems for real components.
  • How to debug overlays without starting an arbitrary z-index escalation.

Introduction

The CSS z-index property helps determine the painting order of boxes that overlap. A box with a higher comparable stacking level is generally painted closer to the user than a box with a lower level. This sounds like a global numerical ranking, but CSS does not maintain one universal z-index scoreboard.

Every relevant box participates inside a stacking context. A stacking context is a self-contained layering environment whose descendants are resolved locally. The entire context is then treated as one atomic unit in its parent context. A child with z-index: 999999 can therefore remain behind a sibling whose parent context has the higher parent-level position.

Most z-index defects are architecture defects rather than insufficient numbers. A transform created an unexpected context, opacity isolated a subtree, a tooltip was clipped by overflow, or a modal was rendered inside a low-level component. Once the context tree and painting order are understood, the correct fix becomes much more predictable.

What z-index Controls

CSS boxes occupy horizontal and vertical positions, and overlapping boxes also have an order along an abstract z-axis. The z-index property assigns an integer stack level or the keyword auto under the rules of the relevant layout and stacking context.

.card-a {
  position: absolute;
  z-index: 1;
}

.card-b {
  position: absolute;
  z-index: 2;
}

If both boxes participate in the same comparable context, card B paints over card A where they overlap. The number is an ordering level, not a physical distance, depth in pixels, or guarantee against every element on the page.

Overlap Comes First

Stacking order becomes visually relevant only where painted regions overlap. Two boxes can have different z-index values without showing any visible difference if their backgrounds, borders, text, shadows, or outlines never occupy the same pixels.

Before debugging z-index, confirm that the boxes actually intersect and that the apparently missing element is not transparent, clipped, transformed off-screen, hidden, or covered by an opaque ancestor background. Layer order is only one possible cause.

Where z-index Applies

The traditional rule says z-index applies to positioned elements: boxes using relative, absolute, fixed, or sticky positioning. Modern layout also allows non-auto z-index values on flex and grid items even when their position remains static. Other specifications define additional participation rules.

.toolbar {
  display: flex;
}

.toolbar__menu {
  z-index: 2; /* Can apply to this flex item without position. */
}

The accurate question is not simply, "Does it have position relative?" Ask which layout rule applies, whether the box forms a context, and which sibling contexts are actually comparable.

What Is a Stacking Context?

A stacking context is an independent group used to resolve painting order. Descendants inside it can be arranged in negative, auto or zero, and positive layers. From the parent context's perspective, however, the group behaves atomically: content from another sibling context does not weave between arbitrary descendants inside it.

The root element creates the root stacking context. Nested contexts form a tree that often resembles the DOM but is not identical to it, because only particular elements create new contexts. Understanding this tree is the foundation of reliable z-index debugging.

The Atomic Context Rule

Consider two positioned parent contexts. Parent A has z-index 1, and Parent B has z-index 2. A dropdown inside Parent A has z-index 10,000. Parent B can still paint above the dropdown because the root first compares Parent A as one unit against Parent B.

.parent-a {
  position: relative;
  z-index: 1;
}

.parent-a .dropdown {
  position: absolute;
  z-index: 10000;
}

.parent-b {
  position: relative;
  z-index: 2;
}

The dropdown's number has meaning only inside Parent A. To fix the conflict, change the parent-level architecture, remove an unnecessary context, or render the overlay in a more suitable high-level location.

Stacking Context vs Stacking Level

A stacking context is the local environment; a stacking level is a box's order within a particular environment. Creating a context both places that element at a level in its parent and creates a new internal world for descendants.

This distinction explains why comparing raw numbers from DevTools can be misleading. The meaningful comparison starts with the nearest sibling contexts under a shared parent, then proceeds inward. Numbers in separate branches are not directly competing.

A Practical Context Tree

Root context
-- Header context: z-index 20
-- Main context: z-index 1
|   +-- Dropdown context: z-index 5000
+-- Modal context: z-index 100

At the root, the modal is above the header, and the header is above the main context. The dropdown's 5000 only orders descendants within Main. It cannot escape above Header while Main remains below Header at the root level.

Common Stacking Context Creators

The root element creates a context. Positioned relative or absolute boxes create one when z-index is not auto. Fixed and sticky boxes create contexts even with auto z-index. Flex and grid items create contexts when their z-index is not auto.

Many visual and containment features also create contexts, including opacity below 1, non-none transforms, certain filters and masks, blending, isolation: isolate, paint containment, and relevant will-change declarations. Top-layer placement introduces a separate high-level mechanism for elements such as modal dialogs and popovers.

This list evolves across CSS modules. When layering changes after a harmless-looking visual effect, inspect the computed styles and specification behavior for that property rather than assuming only position and z-index matter.

Context Creator Reference

TriggerTypical conditionWhy it matters
Root elementAlwaysCreates the root context
Relative or absoluteNon-auto z-indexCreates a numbered local context
Fixed or stickyAny z-indexAlways creates a context
Flex or grid itemNon-auto z-indexWorks without non-static position
OpacityValue below 1Composites descendants as a group
TransformValue other than noneAlso changes containing-block behavior
IsolationisolateCreates an intentional local boundary
Paint containmentRelevant contain valueAdds context, clipping, and containing behavior

Positioned Elements and Non-Auto z-index

A relative or absolute positioned box with an integer z-index establishes a context and takes that stack level in its parent. With z-index: auto, it does not create a context through this rule, although it still participates in positioned painting behavior.

.component {
  position: relative;
  z-index: 0;
}

Zero is an integer, so this rule creates a context. That makes z-index: 0 behaviorally different from auto even though both can appear in a zero-level portion of painting order.

Fixed and Sticky Elements

Fixed and sticky positioned boxes form stacking contexts even when z-index is auto. This helps make their persistent layers self-contained but also means descendants cannot compete directly with content outside those contexts.

.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
}

Assign an intentional parent-level stack when a sticky header must stay above scrolling content. Avoid a global maximum unless the header truly belongs above every dialog, toast, and user-opened surface.

Flex and Grid Items

A flex or grid item with a non-auto z-index can create a stacking context even if position is static. Grid painting also considers order-modified document order, so visual item order can affect equal-level painting.

.grid {
  display: grid;
}

.grid__featured {
  z-index: 1;
}

Do not add relative positioning solely from an outdated belief that z-index otherwise cannot apply. Verify whether the element is actually a flex or grid item and whether a new context is desirable.

Opacity Creates a Context

An element with opacity less than 1 creates a stacking context so the browser can composite the element and its descendants as a group before applying transparency. Even opacity: 0.999 has structural layering consequences despite looking almost fully opaque.

.panel {
  opacity: 0.99;
}

A tooltip inside this panel can become trapped below a sibling context. Avoid tiny opacity changes as rendering hacks, and place transparency on the specific decorative layer rather than an entire interactive subtree when possible.

Transforms Create a Context

Any transform value other than none creates a stacking context for a box governed by the CSS box model. It also establishes a containing block for absolute and fixed descendants. A seemingly neutral transform can therefore change both layer competition and coordinate reference.

.animated-card {
  transform: translateZ(0);
}

Do not spread transform-based performance hacks across components. Profile first, and remember that removing a transform may solve a modal, tooltip, or fixed-position bug by restoring the intended context and containing block.

Filters, Blending, and Visual Effects

Filters, backdrop effects, masks, clip paths, blending, and related rendering features can create stacking contexts under their defining specifications. These declarations often appear on decorative wrappers far above the overlay that later fails.

Search the entire ancestor chain, including utility classes and animation states. The closest element with z-index is not necessarily the context boundary responsible for the problem.

Containment and will-change

Paint containment creates a stacking context and also affects clipping and containing blocks. Content visibility can enable containment behavior. The will-change property can predeclare changes that cause the browser to establish structures associated with the named property.

.dashboard-widget {
  contain: paint;
}

Containment can improve isolation and performance, but it is not a free optimization. Apply it only when its layout, painting, clipping, positioning, and accessibility consequences match the component.

isolation: isolate

isolation: isolate intentionally creates a stacking context without requiring an artificial transform or opacity change. It is valuable for reusable components whose internal positive and negative layers should not interact unexpectedly with outside content.

.card {
  position: relative;
  isolation: isolate;
}

.card__background {
  position: absolute;
  inset: 0;
  z-index: -1;
}

The negative background stays inside the card's context instead of falling behind unrelated page backgrounds. Intentional isolation communicates architecture more clearly than a mystery translateZ(0).

z-index: auto vs z-index: 0

Auto lets the element participate according to the normal stacking rules and, for relative or absolute positioning, does not create a new context through z-index. Zero assigns an integer stack level and creates a context where that rule applies.

Changing auto to zero can trap descendants even if the element's apparent order against siblings does not change. Use zero when local isolation is intended, not as a harmless default reset.

Positive z-index Values

Positive child contexts are painted in increasing stack-level order, with higher comparable values later and therefore nearer the user. If values are equal, tree order can decide which paints later.

A value of 2 does not have two pixels or two layers of separation from 1. Numbers only establish relative ordering within one comparison group, so small semantic scales are sufficient.

Negative z-index Values

Negative levels place positioned child contexts behind zero and positive content within the same parent context. They do not necessarily place content behind the entire page. Parent backgrounds, context boundaries, and painting steps determine the final result.

Use negative values for controlled decorative layers inside an isolated component. Large negative numbers often make content appear to vanish behind an ancestor or canvas without improving the architecture.

Equal Levels and Source Order

When comparable stacking contexts have the same stack level, tree order commonly determines which one paints later. A later sibling can therefore appear above an earlier sibling without either declaring a larger number.

.first,
.second {
  position: absolute;
  z-index: 5;
}

If DOM order communicates meaning or interaction sequence, do not reorder it merely to fix paint. Assign intentional layers or restructure the visual wrappers while preserving logical source order.

A Simplified Painting Order

Inside a stacking context, painting broadly proceeds from the context background and border, through negative child contexts, in-flow blocks, floats, inline content, zero-level positioned content and contexts, and then positive child contexts. The complete algorithm contains more detail, but this model explains many outcomes.

Painting order is recursive. Every child stacking context performs its own internal sequence, then appears atomically in its parent sequence. This recursion is why a flat list of computed z-index values cannot represent the final page.

z-index Does Not Fix Clipping

Stacking decides what paints in front where painting is allowed. Overflow clipping, masks, clip paths, and containment decide which parts are allowed to paint. A tooltip clipped by overflow: hidden cannot escape by increasing z-index.

.card {
  overflow: hidden;
}

.card__tooltip {
  position: absolute;
  z-index: 9999; /* Still clipped by the card. */
}

Move the floating surface outside the clipping ancestor, redesign the clipping wrapper, or use an appropriate overlay or top-layer mechanism. Diagnose clipping and stacking separately.

z-index Does Not Change Geometry

Z-index does not move a box horizontally or vertically, resize it, create overlap, or change its containing block. Position, transform, margins, layout, and inset properties determine geometry. Z-index only contributes to ordering where painted content overlaps.

If a dropdown is in the wrong location, fix its positioning reference before its layer. A correctly stacked box can still be unusable when anchored to the wrong container.

The Top Layer

Some platform features place elements in a top layer managed above ordinary document stacking contexts. Modal dialogs shown with showModal() and open popovers are important examples. Their order is not solved by assigning increasingly large document z-index values.

Use native top-layer features when they match the interaction. They reduce ordinary stacking-context traps, but the component still needs correct semantics, focus behavior, dismissal, sizing, and responsive design. The ::backdrop pseudo-element can style the associated backdrop.

Modal Layering Architecture

A custom modal rendered inside a transformed card can remain below a header despite a huge z-index. Many application frameworks render global overlays near the document root through a portal, placing them outside low-level component contexts and clipping ancestors.

DOM relocation is not a complete modal solution. The implementation must label the dialog, move and contain focus appropriately, make the background inert, restore focus, block unintended pointer interaction, and handle dismissal. Visual order and interaction state must agree.

Dropdown and Tooltip Architecture

Local dropdowns can remain inside a component when the component does not clip them and its parent context is high enough. Global tooltips and collision-aware menus often benefit from an overlay root or platform popover behavior so they can cross component boundaries.

When moving content, preserve accessible ownership and keyboard behavior. Visual proximity alone does not communicate relationships to assistive technology. The trigger, popup role, expanded state, labels, focus model, and dismissal rules remain essential.

Sticky Headers and Table Layers

A sticky header usually needs only a modest local level above scrolling page content. Sticky table header cells similarly need to paint above body cells, not above every modal and toast in the application.

.data-table th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: white;
}

Use the smallest context that solves the overlap. A local value of 1 inside the table is easier to reason about than consuming a global layer token.

Component-Local Layering

Most badges, image shades, decorative backgrounds, and internal controls should use a local component context. Isolation establishes a boundary, and small values describe the relationships inside it.

.media-card {
  position: relative;
  isolation: isolate;
}

.media-card__image { z-index: 0; }
.media-card__shade { position: absolute; inset: 0; z-index: 1; }
.media-card__label { position: absolute; z-index: 2; }

This prevents unrelated cards from competing through arbitrary internal numbers and makes the component portable.

Global Layer Tokens

Global surfaces such as navigation, drawers, backdrops, dialogs, and toasts benefit from a documented layer scale. Names communicate purpose more effectively than unexplained integers.

:root {
  --layer-base: 0;
  --layer-dropdown: 100;
  --layer-sticky: 200;
  --layer-drawer: 600;
  --layer-backdrop: 800;
  --layer-dialog: 900;
  --layer-toast: 1000;
}

The exact values matter less than ownership and consistency. Leave modest gaps only when intermediate relationships are genuinely expected; gaps do not solve incorrect context placement.

Avoiding the Z-Index Arms Race

When one team assigns 9999 and another responds with 99999, the system loses meaning while context traps remain unsolved. Establish component-local layers, reserve global tokens for global surfaces, and review every new context creator.

Document why each global layer exists and which surfaces may appear above it. Removing unused contexts is often safer than introducing another number.

Layer Ownership in Design Systems

A shared application needs clear ownership of global layers. Component authors should control only internal relationships, while the design system owns cross-application surfaces such as navigation, drawers, dialogs, notifications, and emergency banners. This prevents a local card or date picker from silently claiming a global priority.

Review layer tokens alongside overlay behavior, not as isolated CSS constants. Document whether a surface blocks interaction, whether it uses the top layer, where its DOM is rendered, what may appear above it, and how nested popovers behave. Automated visual tests should cover representative combinations rather than testing each component only by itself.

Pseudo-Elements and Layering

::before and ::after participate in the originating element's painting and stacking environment. They are useful for local backgrounds, highlights, and overlays, but negative z-index pseudo-elements can disappear behind ancestor backgrounds without an intentional component context.

Use isolation and clear local levels when a pseudo-element sits behind content. Keep semantic information in real HTML; generated visual layers should remain decorative.

3D Transforms

Three-dimensional transforms introduce depth, perspective, and flattening behavior beyond ordinary integer z-index ordering. Transformed descendants can participate in 3D rendering contexts, and elements can visually interpenetrate in ways a simple layer scale does not describe.

Keep 3D scenes locally contained and test fallback and interaction behavior. Do not use translateZ as a generic z-index replacement or performance switch.

Accessibility and Interaction

Layering changes visual presentation but not DOM order, accessible relationships, or keyboard sequence. A modal painted above the page does not automatically prevent focus from reaching controls behind it. A decorative overlay above a button can also intercept pointer input while keyboard activation still reaches the button.

Ensure visual, pointer, focus, and accessibility states agree. Use semantic controls, inert background content for active modal layers, visible focus indicators, and appropriate popup relationships. Test both forward and backward keyboard navigation.

Focus and Persistent Layers

Fixed headers, sticky footers, and non-modal overlays can completely obscure a focused component. A correct z-index can therefore create an accessibility failure if the persistent layer covers the user's current interaction point.

Reserve scroll space, use scroll padding, keep persistent surfaces compact, and make user-opened panels dismissible. At high zoom, consider returning sidebars and toolbars to normal flow so reading content and focused controls remain visible.

Responsive and Zoom Testing

A layer hierarchy that works on desktop can fail when columns collapse, menus become drawers, or text wraps. The same component may move from local dropdown to global overlay at a breakpoint, which changes its context needs.

Test narrow containers, 400 percent zoom equivalents, reduced viewport height, mobile keyboards, long translations, and print. Look for clipping, covered focus, unreadable transparent overlaps, and controls that remain visually behind an active surface.

Performance Considerations

Stacking contexts and compositor layers are related concepts but not interchangeable. Browsers may promote content for performance, while a CSS stacking context describes painting isolation. Adding transforms or will-change everywhere can consume memory and introduce context or containing-block changes without improving performance.

Profile actual rendering and scrolling. Build a correct layering model first, then optimize demonstrated bottlenecks with measured changes.

Debugging Workflow

First confirm overlap and determine whether the problem is stacking, clipping, geometry, visibility, or pointer interception. Inspect the target's computed position and z-index, then identify the nearest stacking context it belongs to.

Walk upward through every ancestor. Look for non-auto positioned z-index, fixed or sticky positioning, flex or grid item levels, opacity, transforms, filters, blending, isolation, containment, content visibility, and will-change. Record the sibling contexts that compete at each parent level.

Next inspect overflow, masks, clip paths, and paint containment. Temporarily disable suspected context creators and clipping rules one at a time. Compare equal-level source order and use browser stacking or layer tools where available. Fix the highest incorrect context relationship rather than inflating a deeply nested child.

Testing Strategy

Test each overlay against headers, drawers, sticky tables, tooltips, toasts, consent notices, and other surfaces that can be active simultaneously. Verify the expected visual order at the top, middle, and bottom of scroll containers and after responsive breakpoints change the component structure.

Navigate with keyboard focus in both directions and confirm that an inactive surface cannot receive focus or pointer input through the active layer. Test zoom, narrow widths, reduced viewport height, long translated labels, and the on-screen keyboard. Check that focus indicators remain visible above backgrounds and below no unintended overlay.

Include screenshots for common layer combinations and behavior tests for modal inertness, dismissal, and focus restoration. Visual snapshots can catch paint-order regressions, while interaction tests catch defects that a correct screenshot cannot reveal.

Common Mistakes

The most common mistake is treating z-index as a global score. A huge child value cannot escape a lower parent context. Another is confusing overflow clipping with stacking; no layer number removes an ancestor's clip.

Projects also create accidental contexts through tiny opacity changes, neutral transforms, blanket relative positioning, or premature will-change. Negative levels then disappear behind backgrounds, while global overlays remain trapped in component wrappers.

Avoid random values and universal z-index rules. Keep normal content at auto, isolate components intentionally, use named global layers sparingly, and choose native top-layer features when they match the interaction.

Comparison Summary

ConceptPurposeImportant limitation
z-indexAssigns a local stack levelNot a global page score
Stacking contextGroups descendant painting atomicallyChildren cannot escape it
Source orderBreaks many equal-level tiesShould preserve meaningful DOM order
Overflow clippingLimits where content can paintCannot be defeated by z-index
Top layerHosts platform-managed overlaysStill requires correct component behavior
isolationCreates an intentional local contextAlso contains descendant competition

Common Interview Questions

What does z-index do? It assigns a stack level that helps determine painting order within the applicable stacking context.

Does the highest z-index always appear on top? No. Values are compared within context relationships, and a child cannot outrank a higher sibling context above its parent.

What is a stacking context? It is an independent painting group resolved internally and treated atomically within its parent context.

Does z-index require positioning? It applies to positioned boxes, and modern Flexbox and Grid also support it on their items without non-static positioning.

What is the difference between auto and zero? Zero is an integer that can create a context where auto would not.

Can a child escape its parent's context? No. The parent context must be reordered, removed, or bypassed through a different architecture.

Does opacity create a context? An opacity value below 1 does.

Does transform create a context? A non-none transform does and also establishes containing blocks for positioned descendants.

Can z-index fix overflow hidden clipping? No. Clipping and stacking are separate mechanisms.

Why use isolation isolate? It deliberately creates a local stacking context without an unrelated visual effect.

Practical Review Checklist

  • Confirm the elements actually overlap before changing z-index.
  • Identify the target's nearest stacking context and its parent context.
  • Compare sibling contexts before comparing descendant numbers.
  • Check positioning, Flexbox, Grid, opacity, transforms, filters, and containment.
  • Distinguish stacking problems from clipping and geometry problems.
  • Use small local levels inside isolated components.
  • Reserve named global tokens for genuinely global surfaces.
  • Avoid arbitrary huge values and transform performance hacks.
  • Use top-layer platform features when they fit the interaction.
  • Keep DOM, reading, and focus order logical.
  • Prevent overlays and persistent layers from hiding focused controls.
  • Test narrow layouts, zoom, mobile keyboards, long text, and print.
  • Use DevTools to inspect the whole ancestor chain.
  • Fix the context architecture rather than escalating a trapped child.

Key Takeaway

Z-index is a local stacking level, not a global power score. Stacking contexts divide the page into atomic painting groups, and the parent contexts are compared before descendant values. That is why a dropdown at 10,000 can remain below a sibling context at 2.

When a layer appears in the wrong place, identify its context tree, inspect accidental context creators, and separate stacking from clipping and geometry. Use local isolation for components, a small named scale for global surfaces, and native top-layer features for appropriate overlays. This architecture makes layering predictable without an endless competition for the largest number.