Search code examples
csspseudo-element

CSS bug brings pseudo-elements between background and text on position fixed


Seems like I just found a bug or something. Usually when an element has a pseudo-element and I want it to show behind its parent I use z-index:-1. This works fine if the element has position relative or absolute but on position fixed something weird happens: the pseudo-element comes between the background and the text of the element like this:

div {width:200px;height:100px;position:fixed;background:black;display:block;}
div::after {content:"";position:absolute;top:0;width:100%;height:100%;background:red;z-index:-1;display:block;}
<div>
 example
</div>

Can this be fixed so the pseudo-element goes completely behind the parent as it does with the other positions?

Thank you.


Solution

  • The behavior your are experiencing is due to stacking contexts in CSS:

    A stacking context is formed, anywhere in the document, by any element in the following scenarios:

    • […]
    • Element with a position value absolute or relative and z-index value other than auto.
    • Element with a position value fixed […]

    So when you use position: fixed on the parent, it becomes a new stacking context, whereas when you use position: absolute or position: relative without a z-index, it is not a new stacking context, which is why you see this discrepancy in behavior.

    When the parent element is a stacking context it becomes a "container" for position stacking. The text or other elements inside it are by default at the stacking position 0 but the pseudo element in your example has z-index of -1 so it goes behind the text. It does not go behind the parent because the parent itself is the container. It is like you have all these elements in a box and elements can't go outside the box.

    So to have the pseudo element be behind its stacking context parent, we can use a 3D transform to translate the pseudo element behind the plane of the parent. We add transform-style: preserve-3d so "that the children of the element should be positioned in the 3D-space" and then we can add transform: translateZ(-1px) to push the child element behind:

    div {
      width:200px;
      height:100px;
      position:fixed;
      background:black;
      display:block;
      transform-style: preserve-3d;
    }
    div::after {
      content:"";
      position:absolute;
      top:0;
      width:100%;
      height:100%;
      background:red;
      z-index: -1;
      display:block;
      transform: translateZ(-1px);
    }
    <div>
     example
    </div>