anila.

How Layers Really Work in CSS Stacking Contexts

author avatar of anila website

In CSS, when you need to precisely control the front to back stacking order of elements on a page (i.e., their hierarchy on the Z-axis), understanding Stacking Contexts becomes crucial. It is a three dimensional concept used to describe how HTML elements are stacked and aligned on the Z-axis, the axis perpendicular to the screen surface, which determines the visual hierarchy of the elements.

Simply put, you can think of a stacking context like layers in Photoshop or other drawing software. Each stacking context is an independent "layer group". Elements within it are stacked according to their own rules, and this "layer group" itself also occupies a stacking level within its parent's stacking context.

How Are Stacking Contexts Formed?

Not all elements create stacking contexts. For an element to form its own stacking context, it usually needs to meet specific CSS property conditions. Here are some common situations that create new stacking contexts:

  1. The root element (<html>): The document's own root element creates a root stacking context.
  2. Positioned Elements with a z-index value other than auto:
    • position property is relative or absolute, and z-index has an integer value (e.g., 1, 0, 1).
    • position property is fixed or sticky (these elements always create new stacking contexts, even if z-index is auto).
  3. Elements with an opacity property value less than 1: e.g., opacity: 0.5;.
  4. Elements with a transform property value other than none: e.g., transform: scale(1.1);.
  5. Elements with a filter property value other than none: e.g., filter: blur(5px);.
  6. Elements with a perspective property value other than none: e.g., perspective: 1000px;.
  7. Elements with a clip-path property value other than none.
  8. Elements with mask / mask-image / mask-border property values other than none.
  9. Elements with an isolation property value of isolate: This property is specifically designed to create a new stacking context.
  10. Elements with a mix-blend-mode property value other than normal.
  11. Elements with a webkit-overflow-scrolling property value of touch.
  12. Elements with a will-change property specifying any property that would create a stacking context (e.g., will-change: transform;).
  13. Elements with a contain property value of layout, paint, strict, or content.
  14. Flex Items or Grid Items: If their z-index value is not auto, and their parent element is display: flex or display: grid.

Stacking Order Rules

Once a stacking context is formed, all elements within it will be strictly stacked in the following explicit order (from lowest to highest level, i.e., from visually back to front):

  1. The background and borders of the element forming the stacking context.
  2. Child stacking contexts with negative z-index values: The smaller the z-index value, the further back it is.
  3. Non-positioned block-level elements: In order of HTML appearance.
  4. Non-positioned floating elements: In order of HTML appearance.
  5. Non-positioned inline elements: Including inline-blocks, in order of HTML appearance.
  6. Child stacking contexts with z-index: auto or 0: And positioned elements with z-index: auto or 0, in order of HTML appearance.
  7. Child stacking contexts with positive z-index values: The larger the z-index value, the further forward it is.

Important Notes on z-index:

  • z-index is only effective within its own stacking context.
  • An element's z-index value is only compared against the z-index values of its sibling elements (within the same parent stacking context).
  • Crucially: Once an element creates its own stacking context, the z-index values of all its child elements are only compared and sorted within that newly formed context. These child elements cannot "penetrate" or "jump out" of this parent stacking context to directly compare z-index with elements in other external stacking contexts.

Behavior of Stacking Contexts

  1. Independence: Each stacking context is independent. When an element creates a stacking context, all its child elements are stacked within this context.
  2. Atomicity: From the perspective of a parent stacking context, a child stacking context is treated as an indivisible whole—an "atomic" unit. This means that regardless of how high the z-index values of elements within that child stacking context are, their stacking levels are entirely encapsulated within that child stacking context. Therefore, when this child stacking context as a whole participates in the stacking order of its parent context, its internal z-index values are invisible to the outside; as a whole, it cannot cause any of its parts to be displayed above elements outside the parent stacking context that have a higher z-index than the child stacking context itself (as determined by the z-index of its creator element).

Example

Consider the following structure:

html

<div id="parent" style="position: relative; z-index: 1;">
  <div id="child1" style="position: relative; z-index: 999;">Child Element 1 (inside parent)</div>
</div>
<div id="sibling" style="position: relative; z-index: 2;">Sibling Element</div>

Even though child1 has a z-index of 999, it will never appear above sibling (which has a z-index of 2). This is because child1's stacking order is determined within the stacking context created by its parent element, parent. And parent (as a stacking context) has a z-index of 1, which is less than sibling's z-index (2). Therefore, the entire parent stacking context (including all its child elements) will be rendered behind the sibling stacking context.

Pseudo elements in Stacking Contexts

The stacking behavior of pseudo-elements (like ::before and ::after) is closely related to their "host" element, the element they belong to.

  • If pseudo-elements are non-positioned, they are stacked according to the above rules based on their effective position within the host element's content (e.g., as inline elements).
  • If pseudo-elements are positioned (e.g., position: absolute;), their stacking behavior follows the rules for positioned elements. Their z-index values are compared within the stacking context that their host element belongs to or creates.

Pseudo element Example

Suppose we have an element and add a positioned ::before pseudo-element to it:

html

<div class="container">
  <div class="element-with-pseudo">
    <p>This is the element's content text.</p>
  </div>
</div>

css

.container {
  /* Optional: If the container creates a stacking context, it affects the z-index comparison basis for element-with-pseudo */
}

.element-with-pseudo {
  position: relative; /* Creates a stacking context */
  background-color: #lightcoral;
  padding: 20px;
  z-index: 1; /* element-with-pseudo is at level 1 in its parent stacking context */
  border: 2px solid darkred;
}

.element-with-pseudo::before {
  content: "Pseudo-element";
  position: absolute;
  top: -15px;
  left: -15px;
  width: 120px;
  height: 50px;
  background-color: rgba(0, 0, 255, 0.5); /* Semi-transparent blue background */
  color: white;
  padding: 5px;
  text-align: center;
  /* z-index behavior: */
  /* 1. If z-index: -1; the pseudo-element will be behind the background and border of .element-with-pseudo (rules #1 and #2) */
  /* 2. If z-index: 0; or z-index: auto; (or not set), the pseudo-element will be in front of the .element-with-pseudo's content, but above its background and border (rule #6) */
  /* 3. If z-index: 2; the pseudo-element will be above .element-with-pseudo's content (if the content also has a z-index) and .element-with-pseudo itself (rule #7) */
  z-index: -1; /* Example: Places the pseudo-element behind its parent element's background */
}

.element-with-pseudo p {
  position: relative; /* To use z-index to control text level */
  z-index: 1; /* Ensures text content is above pseudo-elements with z-index 0 or auto */
  margin: 0;
  color: darkblue;
}

In this example:

  • .element-with-pseudo creates its own stacking context due to position: relative and z-index: 1.
  • Its ::before pseudo-element is absolutely positioned. If the ::before's z-index is set to 1, it will be rendered behind the background and border of .element-with-pseudo (according to rules #1 and #2 within the stacking context).
  • If the ::before's z-index is set to 0 (or auto), it will be rendered above the background and border of .element-with-pseudo, but before or after its child elements (like the <p> tag, if the <p> also has its own stacking context or a higher z-index), depending on HTML order and the <p>'s z-index.
  • If the <p> element also has position: relative and a z-index, it will also participate in the stacking order within .element-with-pseudo's stacking context.

Parent Loses Stacking Context

Let's revisit the "Pseudo-element Example" and see what happens if the .element-with-pseudo parent element does not form a stacking context. We can achieve this by removing its z-index: 1 (so it becomes z-index: auto while keeping position: relative).

Modified CSS for .element-with-pseudo:

css

.element-with-pseudo {
  position: relative; /* Still positioned */
  background-color: #lightcoral;
  padding: 20px;
  /* z-index: 1; IS REMOVED, so z-index defaults to 'auto' */
  border: 2px solid darkred;
}

/* ::before and p styles remain the same as the original example: */
.element-with-pseudo::before {
  content: "Pseudo-element";
  position: absolute; /* ...other styles... */
  z-index: -1;
}
.element-with-pseudo p {
  position: relative; /* ...other styles... */
  z-index: 1; /* or z-index: auto; */
}

Analysis of Changes

  1. No New Stacking Context by .element-with-pseudo: Since .element-with-pseudo now has position: relative and z-index: auto (and assuming no other properties like opacity < 1 are creating one), it no longer forms its own stacking context.
  2. ::before's z-index: -1 Behavior:
    • The z-index: -1 of the ::before pseudo-element is now interpreted relative to the nearest ancestor stacking context (e.g., the root <html> element, or another ancestor you might have).
    • Crucially, a positioned descendant with a negative z-index (like ::before) whose parent does not form a stacking context will be stacked behind its originating element (.element-with-pseudo) within that ancestor stacking context.
    • Visual Outcome: The ::before pseudo-element will now appear behind the background and border of .element-with-pseudo. If .element-with-pseudo has an opaque background, the ::before might be completely hidden.
  3. <p> Element's Behavior:
    • The <p> element (whether its z-index is 1 or auto) will have its z-index also interpreted relative to the same ancestor stacking context as ::before.
    • .element-with-pseudo itself is at z-index: auto (effectively level 0) within that ancestor stacking context.
    • The content of <p> (text) naturally renders on top of its parent's (.element-with-pseudo's) background.
    • Visual Outcome: Since the ::before is now behind .element-with-pseudo's background, the <p> text will appear on top of both the ::before pseudo-element and the background of .element-with-pseudo.

Key Takeaway

The presence or absence of a stacking context on a parent element dramatically changes how the z-index of its children (especially those with negative z-index) is interpreted. When the parent doesn't form a stacking context, a child's z-index: -1 can cause it to be rendered behind its own parent. This is a common source of confusion when z-index doesn't behave as expected.

Why Does z-index Sometimes Seem Ineffective?

When you find that z-index isn't working as expected, it's usually because:

The element hasn't created a stacking context: z-index only applies to positioned elements (where z-index is not auto) or those elements that create a stacking context by default. Elements are in different stacking contexts: The elements you're trying to compare z-index for might be in different stacking contexts. Their stacking order is determined by the z-index of their respective stacking contexts. Parent element limitation: As shown in the example above, a child element's z-index is limited by its parent stacking context's z-index.

Summary

Understanding stacking contexts is key to mastering CSS layout and layering. When you encounter z-index issues, first check if the relevant elements have created stacking contexts and which stacking context they each belong to. The Layers panel in browser developer tools can often help visualize and debug stacking contexts.

contact
contact icon
contact iconcontact iconcontact iconcontact iconcontact icon

Feel free to follow me or reach out anytime! Open to work opportunities, collaborations, and connections.

Copyright © anila. All rights reserved.