Monday, October 12, 2026

What Is the Stack Order of a Web Page? How `z-index` Works

Web pages are not merely flat collections of text and images. Elements can overlap: a menu may appear over a photograph, a tooltip over a button, or a dialog over the entire page. The browser decides which elements appear in front by following a set of rules known as the stacking order.

The CSS property `z-index` can influence that order, but it does not work as a universal measure of depth. Its effect depends on the element’s stacking context and on other CSS rules. Understanding these relationships makes it easier to build predictable layouts and diagnose elements that appear behind the wrong content.

## What Is Stack Order?

The stack order is the order in which overlapping elements are painted on the screen. An element painted later generally appears in front of elements painted earlier, provided they overlap and belong to comparable stacking layers.

A useful mental model is a stack of transparent sheets. Each sheet contains a page element. Sheets nearer the viewer hide parts of sheets below them. In CSS, however, the stack is not one simple list. Elements are grouped into stacking contexts, and each context has its own internal order.

This distinction explains why assigning a very large `z-index` to an element does not always bring it to the front. The number is considered within the element’s stacking context; it cannot necessarily escape that context to compete directly with elements elsewhere on the page.

## The Default Painting Order

When elements overlap and no special stacking rules change their position, the browser uses a defined painting order. In broad terms, the sequence is:

1. The background and border of the root element are painted. 2. Backgrounds and borders of relevant block-level elements are painted. 3. Positioned elements with a negative `z-index` are painted. 4. In-flow block-level content is painted. 5. Floated elements are painted. 6. Inline content, such as text, is painted. 7. Positioned elements with `z-index: auto` or `z-index: 0` are painted. 8. Positioned elements with positive `z-index` values are painted.

This is a simplified overview, not a complete account of every rendering detail. Tables, inline blocks, flex and grid items, and other categories have specific rules. The key principle is that stack order is determined by categories and contexts, not by the order of CSS declarations alone.

When two elements occupy the same relevant layer, their position in the document can matter. In many common cases, a later element in the HTML appears in front of an earlier one. That behavior is useful, but it should not be treated as a replacement for understanding stacking contexts.

## What `z-index` Does

The `z-index` property specifies an element’s position along the stacking axis: higher values are generally painted in front of lower values within the same stacking context.

```css .panel { position: relative; z-index: 2; }

.notice { position: relative; z-index: 5; } ```

If these elements overlap and belong to the same stacking context, `.notice` will generally appear above `.panel`. The values do not need to be consecutive. A `z-index` of `5` is not five times more prominent than `1`; the browser compares the values to determine their order.

`z-index` accepts integer values, including negative numbers, as well as the keyword `auto`. A negative value can place an element behind other content in its stacking context. However, it does not automatically place the element behind the page background, and it can make content difficult to see or interact with.

## Positioning and `z-index`

Traditionally, `z-index` has an effect on positioned elements—those with a `position` value of `relative`, `absolute`, `fixed`, or `sticky`. For example:

```css .card { position: relative; z-index: 1; } ```

An element with `position: static`, the default, does not normally respond to `z-index` in the same way. Changing `position` solely to make `z-index` work can affect layout, so it is worth checking whether the element is in the correct stacking context before changing its positioning.

Flex and grid items are a notable exception to the traditional rule. A flex item or grid item can use `z-index` even when its `position` remains `static`.

```css .layout { display: grid; }

.layout__item { z-index: 2; } ```

The precise effect still depends on the surrounding stacking contexts and the other elements involved.

## Stacking Contexts: The Essential Concept

A stacking context is a self-contained group of elements whose stacking order is calculated independently. Once the browser has determined the order inside that group, it treats the group as a single unit when comparing it with elements outside the group.

Several CSS properties can create a stacking context. Common examples include:

- The root element of the document. - An element with a positioned `position` value and a `z-index` other than `auto`. - A flex or grid item with a `z-index` other than `auto`. - An element with `opacity` below `1`. - An element with a `transform` other than `none`. - An element with `filter` or `perspective` set to a value other than `none`. - An element with `isolation: isolate`. - Certain uses of `will-change`, `mix-blend-mode`, or containment properties.

These properties are often added for visual effects or performance reasons, so they can create stacking contexts without an obvious intention to change layer order.

### Why a Large `z-index` May Not Work

Consider this simplified structure:

```html

Header
```

```css .sidebar { position: relative; z-index: 1; opacity: 0.99; }

.popup { position: absolute; z-index: 9999; }

.header { position: relative; z-index: 2; } ```

The popup’s `z-index` of `9999` is compared with elements inside the sidebar’s stacking context. The sidebar itself is then compared with the header. Since the sidebar’s context is at `z-index: 1` and the header is at `z-index: 2`, the entire sidebar context is painted behind the header. The popup cannot use its large value to rise above the header.

The solution is usually to adjust the stacking relationship of the parent contexts, or to move the popup into a more appropriate part of the document. Increasing the child’s number alone will not overcome the parent’s position.

## `auto` and Zero Are Not Identical in Every Situation

`z-index: auto` is the default. It generally leaves an element in the ordinary stacking order. By contrast, `z-index: 0` explicitly assigns a stack level and, in relevant cases, creates a stacking context.

This difference can affect how descendants are grouped and compared with neighboring elements. As a result, changing `z-index: auto` to `z-index: 0` may alter the layering of an element’s children, even though both may appear to place the element at a similar level.

When investigating a layering problem, check computed styles rather than relying only on what appears in the stylesheet. A shorthand rule, framework style, or more specific selector may have changed the effective value.

## Parent and Child Relationships

An element’s place in the stacking order is constrained by its ancestors. A child with a high `z-index` cannot normally escape a stacking context created by a parent. This is one of the most common reasons that `z-index` seems ineffective.

It is helpful to inspect the element’s ancestors and look for properties that create stacking contexts, including `opacity`, `transform`, and positioned elements with an explicit `z-index`. The relevant question is not only, “What is this element’s `z-index`?” but also, “Which stacking context does it belong to, and how is that context layered?”

This relationship is particularly important for reusable components. A dropdown, popover, or tooltip might work in one part of a page but appear behind another component when placed inside a transformed or partially transparent container.

## The Top Layer

Some browser features use a special top layer, which sits above ordinary stacking contexts. Examples include modal dialogs opened with the browser’s dialog mechanisms, popovers shown using the popover API, and fullscreen content.

An ordinary `z-index` value cannot move an element above content in the top layer. This is intentional: these features need to appear above regular page content without competing with every author-defined layer. If a native modal appears above a custom menu with an enormous `z-index`, the difference in layering system is likely the reason.

The top layer should not be confused with a very high numeric `z-index`. It is a separate part of the browser’s rendering model.

## A Practical Way to Diagnose Layering Problems

When an element appears behind the wrong content, use a systematic approach:

1. Confirm that the elements overlap. A positioning or layout issue can look like a stacking problem. 2. Check the element’s computed styles. Verify `position`, `z-index`, `opacity`, `transform`, and other relevant properties. 3. Inspect the ancestors. Identify stacking contexts that may constrain the element. 4. Compare the parent contexts. A child’s `z-index` cannot normally outrank content outside its parent context if that parent is painted lower. 5. Look for the top layer. Check whether the competing element is a modal, popover, or fullscreen element. 6. Use browser developer tools. Inspect the DOM, toggle styles, and test changes to parent and child values.

Prefer small, deliberate layer values over arbitrary numbers such as `999999`. A project-wide scale can make relationships clearer:

```css :root { --layer-content: 0; --layer-dropdown: 10; --layer-sticky: 20; --layer-overlay: 30; --layer-modal: 40; } ```

The numbers themselves are not special; consistency is what helps. The scale should also reflect the design and account for stacking contexts, rather than assuming every component participates in one global numeric contest.

## Common Misunderstandings

A high `z-index` does not guarantee that an element will appear above everything else. It cannot overcome a lower ancestor stacking context, and it cannot outrank the browser’s top layer.

Likewise, `z-index` does not create physical depth or change an element’s layout position. It affects painting order, not where the element takes up space in the document. Positioning, margins, grid placement, and transforms determine geometry; stacking determines which overlapping content is painted in front.

Finally, adding `position: relative` to an element may make `z-index` applicable, but it does not necessarily solve the underlying problem. If another ancestor or sibling context controls the relevant order, the parent relationship must be addressed.

## Conclusion

The stack order determines how overlapping content is painted. `z-index` can adjust that order, but its value is interpreted within a hierarchy of stacking contexts. Positioning, opacity, transforms, and other CSS properties can create those contexts, while parent contexts constrain the layers of their children. By examining the full ancestor chain and distinguishing ordinary stacking from the browser’s top layer, developers can understand why an element appears behind another and choose a reliable fix.

No comments:

Post a Comment