There's a common misconception that the CSS properties top
, left
, bottom
, and right
dictate the stacking order of elements, but in reality, it's not the case. By removing the top
property and adding a negative value to margin-bottom
, you can still achieve the desired layout.
.c1 {
position: relative;
margin-bottom: -50px;
}
.c2 {
background: green;
width: 200px;
height: 200px;
}
<div class="c1">
Why am I still visible?
</div>
<div class="c2"> </div>
Understanding the concepts of stack level and stacking contexts sheds light on why the relatively positioned element appears on top. The stack level is influenced by the z-index
property, where elements with z-index: auto
default to stack level 0. As per the spec, elements with lower stack levels are painted first within each stacking context. Check out this section for more details.
- the background and borders of the element forming the stacking context.
- the child stacking contexts with negative stack levels (most negative first).
- the in-flow, non-inline-level, non-positioned descendants.
the non-positioned floats.
- the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks.
- the child stacking contexts with stack level 0 and the positioned descendants with stack level 0.
- the child stacking contexts with positive stack levels (least positive first).
To illustrate this further, let's compare a relatively positioned element that creates a stacking context versus one that doesn't.
const blueBox = document.querySelector('#blue-box');
const button = document.querySelector('button');
const opacity1 = document.querySelector('#opacity-1');
const opacityNot1 = document.querySelector('#opacity-not-1');
let opacity = 1;
button.addEventListener('click', () => {
if(opacity == 1){
opacity = .9;
opacity1.hidden = true;
opacityNot1.hidden = false;
}
else {
opacity = 1;
opacity1.hidden = false;
opacityNot1.hidden = true;
}
blueBox.style.opacity = opacity;
})
#blue-box {
width: 100px;
height: 100px;
position: relative;
background: blue;
}
#yellow-box {
width: 50px;
height: 50px;
position: relative;
top: 75px;
z-index: 1;
background: yellow;
}
#red-box {
width: 100px;
height: 100px;
position: relative;
background: red;
}
<div id="blue-box" style="opacity: 1">
<div id="yellow-box"></div>
</div>
<div id="red-box"></div>
<button>Toggle opacity for blue box</button>
<p id="opacity-1">Blue box has <code>opacity: 1;</code> (so does NOT create a new stacking context)</p>
<p id="opacity-not-1" hidden>Blue box has <code>opacity: .9;</code> (so it creates a new stacking context)</p>
In this scenario, all three boxes are relatively positioned, but the yellow box stands out as it creates a stacking context with a higher stack level. Toggling the opacity of the blue box changes its behavior accordingly.
What happens when the blue box does not create a stacking context? The painting order follows the specified sequence, placing the yellow box above the blue and red boxes due to its elevated stack level.
Conversely, enabling the blue box to create a new stacking context shifts the dynamics. Now, the blue box and its associated stacking context are painted together, leading to the red box being displayed on top of the yellow box.
In summary, elements with position: relative
do not inherently establish a new stacking context but rely on their positioning and stack level to determine display order relative to other elements.