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 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>