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>