
아래는 코드와 해당 코드가 실제 브라우저에서는 어떻게 보이는지를 드랍다운(정답)을 통해서 추가해놓았다. 드랍다운을 클릭하기 전 코드가 어떻게 보이는지를 생각해보면 좀 더 도움이 될 수 있다.
velog에서 드랍다운이 안된다고 한다... 흑흑 😭
각각의 예시를 직접 코드를 만져보면서 동작 원리를 파악해 볼 수 있도록
플레이그라운드 링크를 달아두었다. 또한 예시에는 불필요하다고 보이는 스타일(ex. 마진, 위치값 등)은 요소들의 겹침 현상을 좀 더 잘 보기 위한 장치이기에 아래 예시 코드에서는 삭제하였고, 이는 플레이그라운드 안에 들어가면 확인할 수 있다.중요 포인트인 z-index, position을 중점으로 살펴보길 바란다.
아직 명확하지 않은 부분도 있기에 틀린 부분이 있다면 피드백은 환영입니다. 🙏🏻
기본 쌓임규칙
기본적으로 HTML의 요소들은 나온 순서대로 쌓인다. 즉 우리가 바라보는 모니터를 z축이라고 했을 때, 먼저 랜더링된 요소가 z축의 가장 아래에 있다는 의미이다. 이를 MDN에서는 아래처럼 순서를 정한다.
<style>
.box { /* 공통스타일 */
width: 100px;
height: 100px;
font-size: 24px;
text-align: center;
}
.box-1 {
background-color: yellowgreen;
}
.box-2 {
background-color: dodgerblue;
}
.box-3 {
background-color: tomato;
}
</style>
<body>
<div class="box box-1">1</div>
<div class="box box-2">2</div>
<div class="box box-3">3</div>
</body>

어떤 박스가 가장 위에 있는지 위 코드만으로는 분간하기 어렵기 때문에 .box-2와 .box-3에 음수마진(margin-top : -50px)을 추가하여 이를 확인할 수 있다.(박스2와 박스3이 50px만큼씩 위로 올라가게됨) 결과적으로 박스가 HTML에 구현된대로, 박스1 > 박스2 > 박스3 순서대로 쌓여있는 것을 확인할 수 있다.
<style>
.box { /* 공통스타일 */
padding: 20px;
font-size: 20px;
text-align: center;
color: white;
}
.box-1 {
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 400px;
background-color: greenyellow;
}
.box-2 {
position: relative;
width: 100%;
height: 100px;
background-color: deepskyblue;
}
.box-3 {
position: relative;
width: 100%;
height: 100px;
background-color: darkblue;
}
.box-4 {
position: absolute;
top: 0;
right: 0;
width: 100px;
height: 400px;
background-color: mediumseagreen;
}
.box-5 {
position: static;
width: 100%;
height: 100px;
background-color: tomato;
}
</style>
<body>
<div class="box box-1">absolute box1</div>
<div class="box box-2">relative box2</div>
<div class="box box-3">relative box3</div>
<div class="box box-4">abolute box4</div>
<div class="box box-5">static box5</div>
</body>
그렇다면 포지션 속성이 있는 박스들은 어떻게 보여질까? 위 코드는 박스1~4는 포지션 속성(absolute와 relative)를 주고 박스5는 포지션 속성을 주지 않은 경우의 코드이다. 나의 예상은 abolute는 일반적인 배치문맥을 벗어나는 것으로 알고 있었고, 그래서 abosulte가 위에 떠있고 그 밑에 relative 속성을 가진 요소들이 순서대로 쌓일거라고 생각했다.
하지만 예상과는 다르게, 포지션 속성이 있는 경우도 포지션 속성이 있는 요소들이 HTML에 노출되는 순서대로 쌓인다. 단, position 속성의 기본값은 static이기 때문에, static인 경우(or 포지션 속성이 없는 경우)는 HTML에서의 위치에 관계없이 포지션 속성이 있는 요소보다 밑에 쌓이게된다.

박스5 > 박스1 > 박스2 > 박스3 > 박스4 순서대로 쌓여있다.쌓임 규칙을 변경하는 방법
대부분 z-index 속성은 HTML요소들 사이에서 어떤 것이 위에 보일지를 결정할 때, 해당 속성을 사용한다. 좀 더 그럴싸한(?) 용어를 섞어서 말해보면, 기본적인 쌓임순서(stacking order)를 변경하여 적용하고 싶을 때, z-index를 사용한다.
<style>
.box { /* 공통스타일 */
width: 100px;
height: 100px;
font-size: 20px;
}
.box-1 {
z-index: 30;
background-color: tomato;
}
.box-2 {
z-index: 10;
background-color: dodgerblue;
}
.box-3 {
z-index: 20;
background-color: yellowgreen;
}
</style>
<body>
<div class="box box-1">box1</div>
<div class="box box-2">
box2
<div class="box box-3">box3</div>
</div>
</body>
3개의 박스 안에 각각 z-index가 설정되어 있다. 3개의 박스가 어떤 순서대로 쌓일지 생각해보자.
위의 z-index의 정의를 보고 예상해보면, 박스2 > 박스3 > 박스1 순서대로 쌓이지 않을까라고 생각할 수 있다.

오잉? 그런데 위 이미지를 보면 박스1 > 박스2 > 박스3 순으로 쌓여있다. z-index와는 전혀 관계없이 순서가 짜여있는 것을 볼 수 있다. 왜 그럴까. 그것은 바로 z-index만으로 쌓임 순서를 변경할 수 없기 때문이다. z-index는 static(position의 기본값)을 제외한 position 속성과 함께 사용될 때, 새로운 쌓임맥락을 생성하여 z-index가 우리가 예상한대로 작동하게 된다. 그렇다면 위 코드를 어떻게 수정하면 될까?

position: relative을 넣어주면, 우리가 예상했던, z-index순으로 박스가 쌓이는 것을 확인할 수 있다. (박스2(파란색) > 박스3 > 박스1)위 코드에서 본 결과를 더해서, 다시 한 번 z-index에 대해서 설명해보자.
z-index는 기본적인 쌓임순서(stacking order)를 변경하기 위해서 사용하는 속성으로 position 속성과 함께 사용할 때, z-index가 제대로 작동한다. 즉 두가지 속성이 결합되어 작동할 때, 쌓임맥락(stacking context)라는 것이 형성되어 쌓임맥락 내에서만이 비로소 우리가 예상한대로 z-index 값의 크기에 따라서 작동하게 된다.
그런데 자꾸
쌓임맥락이라는 용어가 나오는데 이건 무언데?? 🤯
MDN에 따르면, 가상의 z축을 사용하여 HTML 요소들을 3차원한 개념으로서 Z축은 유저들이 모니터를 바라보는 축을 말한다. 각각의 HTML 요소들은 z-index의 값에 따라서 3차원 공간에서 위치를 차지하게 된다. (쌓임맥락 자체가 요소들의 z축의 위치를 결정하는 것은 아니다.)
쌓임맥락은 다양한 조건들에서 생성이 된다.
<html>)이 외에도 다양한 경우가 있다. 여기에 더 많은 조건들이 나열되어 있으니 참고하시길.
쌓임맥락에 대한 특징을 코드를 통해서 알아보자.
문제
아래 코드는 아래 이미지와 같은 모습으로 구현되어 있다. 아래 조건에 맞추어 코드를 수정하여 Red박스를 Green과 Blue박스 아래로 가도록 만들어봐라. 단, 조건이 있다.

Before

After
<style>
* {
box-sizing: border-box;
}
.span-box { /* 공통스타일 */
width: 100px;
height: 100px;
padding: 5px;
color: white;
text-align: right;
}
.red {
position: absolute;
z-index: 1;
background: red;
}
.green {
position: absolute;
background: green;
}
.blue {
position: absolute;
background: blue;
}
</style>
<body>
<div>
<span class="span-box red">Red</span>
</div>
<div>
<span class="span-box green">Green</span>
</div>
<div>
<span class="span-box blue">Blue</span>
</div>
</body>
opacity : 0.999 를 주면 새로운 쌓임맥락을 형성하게 되면서 red span-box가 부모 div의 쌓임맥락을 벗어날 수 없게 된다. 결과적으로 Green/Blue박스의 밑으로 들어가게 된다.
Before
- html 요소(root 요소) 라는 같은 쌓임맥락 안에서는 div는 노출되는 순서대로 쌓이게된다. 그 다음에 red span-box의 경우에는 독립적인 쌓임맥락을 형성하게 되고, z-index가 작동하여 다른 요소들 위에 쌓이게 된다.
After
- 첫번째 div에 opacity가 추가되면서 해당 요소는 독립적인 쌓임맥락을 형성하게 된다. 하지만 쌓임맥락을 형성한 것이지 z-index에 의해서 z축의 위치가 변경된 것은 아니다. 그렇기 때문에 기존 쌓임규칙대로 쌓이게 된다.
- 그런데 red span-box의 경우는 부모 div가 독립적인 쌓임문맥을 형성했기 때문에 항상 부모의 쌓임문맥 안에서 요소가 배치된다. 즉 첫번째 div는 가장 밑에 쌓여있기 때문에 그 쌓임문맥을 벗어날 수 없는 것이다.
각각의 쌓임맥락은 독립적이고, 쌓임맥락 안의 자식 요소는 절대로 부모 요소의 쌓임맥락을 벗어날 수 없다.(부모 요소 안에서만 비교가 가능하다.)
이제 아래 코드에서 박스가 쌓이는 순서를 알 수 있을까? 4번 예제와 동일한 개념을 묻고 있다.
.box {
width: 100px;
height: 100px;
}
.wrapper {
position: relative;
z-index: 10;
}
.box-1 {
position: absolute;
z-index: 20;
background-color: tomato;
}
.box-2 {
position: absolute;
z-index: 30;
background-color: dodgerblue;
}
.box-3 {
position: absolute;
z-index: 50;
background-color: greenyellow;
}
<body>
<div class="box box-1">box1 20</div>
<div class="wrapper">
<div class="box box-2">box2 30</div>
<div class="box box-3">box3 50</div>
</div>
</body>
박스2 > 박스3 > 박스1 순서대로 쌓인다.

위에서 설명한바와 같다. box-1과 wrapper는 각각의 쌓임맥락을 형성하여 z-index에 작동하게된다. 그 둘이 비교되면 z-index가 큰 박스1이 위에 쌓이게 된다.(box-1의 z-index : 20, wrapper의 z-index : 10) 이와 동시에 wrapper의 자식인 box-1과 box-2는 부모인 쌓임맥락을 벗어날수 없게 되고 결과적으로 아무리 z-index가 10을 넘게 설정한다하더라도 실제적으로 10이상으로 쌓일수 없게 된다. 단, 자신의 부모 안의 요소들끼리는 비교가 가능하다.
이제 반대로 wrapper의 z-index를 제거하면 어떻게 될지 생각해보자.
감각적으로 z-index끼리 비교하게될거고 z-index가 낮은순부터 높은순으로 쌓이게 될 것이라고 예상할 수도 있다. 맞다. 예상대로 쌓인다. 그런데 왜 이렇게 쌓이게 되는거지??
wrapper의 z-index값은 무엇일까??(?? : 무슨 쌩뚝맞은 소리지?? z-index를 삭제했는데...😡 ;;;) z-index의 기본값은 auto이고 auto라는 의미는 부모와 쌓임순서가 동일하게 설정되고 쌓임문맥도 형성하지 않는다. 또한 같은 depth에서 z-index에 대한 비교가 일어나게되면 자신의 자식에게 비교 대상을 넘겨주게 된다. 그래서 위 코드에서 wrapper의 자식인 box-2/3과 box-1의 z-index가 비교되어 쌓이게 된 것이다.
박스1 > 박스2 > 박스3 으로 z-index의 크기 순서대로 쌓인다.

아래 코드에서 박스는 어떻게 쌓이게 될까??
.container {
display: flex;
}
.box {
width: 100px;
height: 100px;
}
.box-1 {
z-index: 20;
background-color: tomato;
}
.box-2 {
z-index: 10;
background-color: dodgerblue;
}
.box-3 {
z-index: 50;
background-color: yellowgreen;
}
<body>
<div class="container">
<div class="box box-1">box1 20</div>
<div class="box box-2">box2 10</div>
<div class="box box-3">box3 50</div>
</div>
</body>
위에서 쌓임맥락이 어떤 경우 형성되는지를 잘 보았다면 쉽게 맞출수 있었을 것이다. flex의 자식들은 쌓임맥락이 형성되기때문에 z-index만 추가하여도 z-index가 잘 작동하게 된다. 그래서 z-index 순서대로 쌓이게 된다.
박스2 > 박스1 > 박스3 순서대로 쌓인다.

6가지 예시를 통해서 요소가 어떤 기준에 의해서 쌓이는지, 그 속에서 z-index가 어떻게 동작하는지 알아보았다. 가장 많이 사용하게되는 내용을 요약해보면 이렇다.
일반적인 쌓임순서는 HTML에 노출되는 순서이다. position 속성의 우선순위는 position 속성이 없는 요소가 먼저 쌓인다. 그 다음이 z-index에 의해 쌓임의 순서가 정해진다.
z-index는 쌓임맥락이 형성되어야 작동한다. 요소 안에 static이 아닌 position 값과 auto가 아닌 z-index값이 있을때 쌓임맥락이 형성되고 z-index가 제대로 작동한다. ( + flex, grid의 자손에서도 z-index는 작동한다. 더 많은 조건들 존재.)
쌓임맥락은 독립적으로 작동하고 각각의 쌓임맥락의 자식 요소는 부모 요소의 쌓임맥락을 벗어날 수 없다.
z-index는 CSS 개념 중에서 가장 헷갈리고 트리키한 것으로 유명합니다. z-index를 남용하게됨으로서 발생하는 문제를 일명 z-index war 라고 부른다고 한다. 그래서 z-index가 어떻게 작동하는지 알아보았고 개념적으로 어느 정도(절대 100% 아님...🥲) 이해할 수 있었다. 그러나 과연 이러한 개념적인 흐름을 이해하고 z-index를 사용할 수 있을까에 대해 묻는다면 장담할 수 없을 것 같다. 그렇다면 내가 할 수 있는 일은 z축 방향으로 쌓아야하는 무엇인가를 구현할 때, 혹은 z-index 관련된 버그가 나타났을 때, 여기서 익힌 키워드를 바탕으로 빠르게 디버깅을 하여 올바른 z-index를 사용할 수 있게 만드는 것이라고 생각한다. 이 글을 보시는 분들이 여기서 나온 키워드를 토대로 누구보다 빠르게 문제를 해결할 수 있길 바란다. 🙏🏻