제가 쉽게 보려고 만든 달래님 블로그 요약본입니다.
부록은 중요한 부가적인 내용을 추가한 내용입니다. 꼭 봐야 할 내용입니다.
TL;DR
z-index의 값이integer이면 local stacking context를 생성한다.z-index의 값이auto거나 명시되어 있지 않다면 local stacking context를 생성하지 않고 상위 stacking context를 물려받는다.
1. z-index 의 개념
- z축은 요소의 3차원적인 깊이를 나타내는 가상의 개념 & 가상의 축입니다.
- 웹에서 요소의 Z축 방향의 깊이를 결정합니다.
- 브라우저는
z-index속성 값이 낮은 요소를 먼저 그리고,z-index속성 값이 높은 요소를 나중에 그리기 때문에 요소가 겹쳐있을 경우z-index속성 값이 큰 요소가z-index속성 값이 작은 요소의 일부를 가리거나 전체를 덮을 수 있습니다. - 양의 정수, 음의 정수를 지정할 수 있습니다.(±integer)
- static(default) 요소는 z-index가
auto로 고정입니다. - non-static 요소 간의 비교는 맨 마지막 부분에서 예를 들어서 설명하는 소수점 자릿수로 이해하면 굉장히 쉽습니다.
2. z-index가 없을 때 요소 간 상대적 깊이
position이 지정되지 않았을 때(default static)
기본적으로 z-index 속성이 적용되지 않은 요소 간에는 HTML 문서 상에서 나중에 나오는 요소가 먼저 나오는 요소보다 위로 올라오도록 되어 있는데요. 따라서 position이 static인(default가 static) 요소 간에는 같은 선상에서는 늦게 오는 요소가 위로 올라갑니다.
position이 지정되어 있을 때
position속성이static(default)이 아닌 요소는 무조건position속성이static인 요소 위로 올라옵니다.position속성이relative나absolute,fixed,sticky인 요소 간에는 HTML 문서 상에서 나중에 나오는 요소가 먼저 나오는 원소 위로 올라옵니다.
3. z-index가 있을 때 요소 간 상대적 깊이
position이 static인 요소는 z-index가 auto로 고정!
position 속성이 static인 요소에는 z-index 속성이 아무 효력을 내지 못한다는 점입니다. 왜냐하면 position 속성이 static인 요소는 z-index 속성이 auto, 즉 0으로 고정되어 있기 때문입니다.
position이 static이 아닌 요소가 z-index를 음수로 주게 되면 static인 요소보다 밑에 위치할 수 있습니다.static보다 밑에 위치하는 방법
4. stacking context: z-index가 비교되는 범위
z-index와 관련되서 가장 오해하기 쉬운 부분은 z-index가 HTML 문서 전체 범위에서 비교된다고 생각하는 것인데요. 사실 z-index는 특정 범위 내에서 비교되며, 이것을 CSS에는 stacking context라고 부릅니다.
사실 이거 기록해두려고 요약 정리를 한 것인데요 :)부모 요소의 z-index가 졌으면 자식 요소가 아무리 높아도 이길 수 없다!
- 부모 요소가 같은 선상의 요소들의 z-index 대결에서 패배하였다면, 그 패배한 부모 요소의 자식요소는 아무리 z-index가 높아도 이웃 요소의 z-index를 이길 수 없습니다.
- 그 이유는
z-index가 서로 다른 범위에서 비교되기 때문입니다.
다음과 같은 코드가 있다고 가정해보겠습니다.
<div class="wrapper">
<div class="first box">1</div>
</div>
<div class="second box">2</div>
.first.box {
z-index: 100;
background: yellow;
position: relative;
top: 50px;
left: 50px;
}
.second.box {
z-index: 2;
background: tomato;
position: relative;
}
.wrapper {
z-index: 1; position: relative;
}
자식 요소의 z-index가 100이어도 이길 수 없습니다.
다음과 같이 **소수점 자리**가 하나씩 늘어나면서 비교한다고 생각하면 (실제로는 그렇지 않겠지만) 이해가 쉽습니다.좀 더 쉽게 이해하려면 소수점 자리로 이해하자
<!-- z-index: 1 -->
<div class="wrapper">
<!-- z-index: 1.100 -->
<div class="first box">1</div>
</div>
<!-- z-index: 2 -->
<div class="second box">2</div>
부모 요소에 z-index가 없고 자식 요소에 position: relative 와 각각의 z-index가 있다면 어떨까요? 사실 상 바로 위에서 얘기한 소수점 자리로 이해한다면 식은 죽 먹기입니다.만약 부모요소에 z-index가 없다면
<div>
<div>
<!-- z-index: 100 -->
<div class="first box">1</div>
</div>
</div>
<div>
<div>
<!-- z-index: 2 -->
<div class="second box">2</div>
</div>
</div>
0.0.100 vs 0.0.002
로 1번이 z-index 대결에서 이겼네요.
주의할 점
z-index에 대해서 제대로 이해하고 않고, z-index를 남용하게 되면 여러 요소를 원하는 순서로 겹치는 것이 생각했던 것보다 상당히 골치아파질 수 있는데요. 워낙 악명이 높아서 CSS 커뮤니티에서는 이 문제를 z-index 전쟁(war)이라고 부르기도 합니다.
부록
1. z-index: auto와 z-index:0은 다르다?! 🤔(feat. local stacking context)
위에서 static인 요소(기본값)는 z-index: auto로 고정되어 있다고 했습니다.
z-index를 규정하지 않는 것은z-index: auto와 동일합니다. 부모의 stacking context를 물려받습니다. 이게 바로z-index의 초기값입니다.- 그러나
z-index: 0이z-index: auto와 같다고 생각한다면 큰 오산입니다.
엥? 똑같은 거 아닌가요?
아래처럼 해봤을 때
.box {
position: relative;
width: 64px;
height: 64px;
top: 32px;
left: 32px;
}
.red {
background: red;
}
.green {
background: green;
}
.blue {
background: blue;
}
<div>
<div class="box red" style="z-index: auto;"> <!-- z-index: auto -->
</div>
<div class="box blue" style="translate: 0 -20px; z-index: 0;"></div> <!-- z-index: 0 -->
</div>
<div>
<div class="box red" style="z-index: 0;"> <!-- z-index: 0 -->
</div>
<div class="box blue" style="translate: 0 -20px;"></div> <!-- z-index: auto -->
</div>

두 경우 다 blue가 이겼는데 0이나 auto나 동일한거 아닌가요?
같은 레벨의 요소끼리는 별 차이가 없어보입니다. 더 뒤에 오는 요소가 위에 쌓이게 되죠. 실제로도 그렇습니다. 하지만 미세하지만 알아둬야 할 중요한 차이가 있습니다.
결론부터 말하자면, z-index: 0는 local stacking context를 만드는 반면에 z-index: auto는 local stacking context를 만들지 않는다는 차이가 있습니다. local stacking context는 stacking context 안의 또 다른 지역 stacking context를 의미하는데 예시를 통해 바로 이해해봅시다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
.box {
position: relative;
width: 64px;
height: 64px;
top: 32px;
left: 32px;
}
.red {
background: red;
}
.green {
background: green;
}
.blue {
background: blue;
}
#example-auto {
display: inline-block;
}
#example-0 {
display: inline-block;
margin-left: 64px;
}
</style>
</head>
<!-- prettier-ignore -->
<body>
<div id="example-auto">
<div class="box red"> <!-- z-index: auto -->
<div class="box green" style="z-index: 1"></div> <!-- z-index: 1 -->
</div>
<div class="box blue"></div> <!-- z-index: auto -->
</div>
<div id="example-0">
<div class="box red" style="z-index: 0"> <!-- z-index: 0 -->
<div class="box green" style="z-index: 1"></div> <!-- z-index: 1 -->
</div>
<div class="box blue"></div> <!-- z-index: auto -->
</div>
</body>
</html>
결과는 아래와 같습니다.

첫 번째는 충분히 예상해볼 수 있는데, 두 번째는 맞추셨나요?
단계별로 알아보겠습니다.
먼저, 첫 번째 구역과는 다르게 red가 z-index: 0을 가지고 있습니다.
blue상자는 red상자의 내부에 중첩되어 있지 않으므로 red상자의 stack context의 일부가 아닙니다 (위에서 z-index를 명시하지 않았을 때 z-index의 기본값은 auto라고 하였습니다.). 대신 부모 스택 컨텍스트인 example-0의 일부입니다. 그런데 example-0에 z-index가 지정되어 있지 않습니다. 그래서 기본값이 auto인 stacking context를 물려받습니다.
blue상자와 red상자의 z-index를 비교할 때(비교할 때 green상자는 red상자 내부에 중첩되어 있으므로 이미 red가 생성한 local stacking context에 포함됨), blue상자는 HTML상에서 더 뒤에 오기 때문에 상대적으로 위에 렌더링됩니다.
그래서 green은 red위에 위치하지만 blue밑에 위치하는 것입니다. green은 red가 만든 local stacking context에 빠져있는 상태인 것이죠. 그래서 한 뭉텅이로 blue와 비교가 되는 것입니다.
리마인드
다시 한 번 리마인드하자면 0이든 다른 정수형 숫자든 local stacking context를 생성합니다. red가 새로 만든 local stacking-context는 얼마나 높이가 높든 간에 blue상자 입장에서는 red의 로컬 구역이니(context=문맥 밖이니) 알 바가 아닙니다.
한 단어로 정리하자면, ‘상대방이 만든 local stacking context는 내가 가질 수 있는 문맥이 아니기 때문에 결코 비교 레벨이 될 수 없다.’ 라고 정리해 볼 수 있을 것 같습니다.
이렇게 보면 꽤 중요한 내용인데도 '부록’으로 뺀 이유는 z-index에 0을 주는 경우는 Root(뿌리)가 되는 부분에 의도적으로 주지 않는 이상 흔하지 않은 경우이기 때문입니다. 그렇지만 auto와 0은 이렇게 다르다는 점은 알고 있어야 합니다.
만약 아직도 헷갈린다면 아래 예제를 가지고 결과를 예상해보며 직접 연습해보시길 바랍니다.🚀
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
.box {
width: 100px;
height: 100px;
}
.red {
background: red;
}
.green {
background: green;
left: 50px;
top: 50px;
}
.blue {
background: blue;
translate: 0 -30px;
}
</style>
</head>
<body>
<div>
<div class="box red" style="position: relative; z-index: auto">
<div class="box green" style="position: relative; z-index: 2"></div>
</div>
<div class="box blue" style="position: relative; z-index: 0"></div>
</div>
<div>
<div class="box red" style="position: relative; z-index: 0">
<div class="box green" style="position: relative; z-index: 2"></div>
</div>
<div class="box blue" style="position: relative; z-index: auto"></div>
</div>
<div>
<div class="box red" style="position: relative; z-index: auto">
<div class="box green" style="position: relative; z-index: 2"></div>
</div>
<div class="box blue" style="position: relative; z-index: auto"></div>
</div>
<div>
<div class="box red" style="position: relative; z-index: 0">
<div class="box green" style="position: relative; z-index: 2"></div>
</div>
<div class="box blue" style="position: relative; z-index: 0"></div>
</div>
</body>
</html>
See the Pen stacking-context & local-stacking-context by Olimjo (@OLIMJO) on CodePen.
☺해설을 좀 해놓자면,
- 첫 번째 경우: blue의 z-index: 0 으로 인해서 blue밑에서 생성될 local stacking context는 red의 비교 대상 밖입니다. 하지만 blue까지는 같은 stacking context를 물려받은 존재입니다. 그래서 painting 순서는 빨강 > 파랑 > 초록 이고, 위에서부터 초록 > 파랑 > 빨강입니다.
- 두 번째 경우: red의 z-index: 0으로 인해서 red 밑에서 생성될 local stacking context는 blue에게는 비교 대상 밖(context 밖! 문맥 밖!)입니다. 그래서 painting 순서는 빨강 > 초록 > 파랑이고, 위에서부터 파랑 > 초록 > 빨강입니다.
- 세 번째 경우: red와 blue 모두 z-index가 auto로서 각자의 local stacking context를 생성하지 않고 부모의 stacking context를 물려받아서 같은 레벨에서 비교하게 됩니다. 그래서 painting 순서는 빨강 > 파랑 > 초록이고, 위에서부터 초록 > 파랑 > 빨강입니다.
- 네 번째 경우: red와 blue 모두 z-index가 0으로서 integer이기 때문에 각자의 local stacking context를 생성합니다. 그래서 서로의 local stacking context는 문맥 밖이기 때문에 비교 대상이 되지 않습니다. 그래서 green은 뒷전이고, 부모로부터 물려받은 같은 stacking context 내에서 red와 blue를 비교하게 됩니다. 그래서 painting 순서는 빨강 > 초록 > 파랑이고, 위에서부터 파랑 > 초록 > 빨강입니다.
z-index를 의도적으로 주는 건 언제일까?
z-index 값이 너무 많아지고 복잡해질 때 의도적으로 줄 수 있습니다. 보통 relative와 z-index: 0을 명시적으로 주는 것을 해결방법으로 제시합니다.
.container {
position: relative;
z-index: 0;
}
예를 들어서 header가 main보다는 항상 위에 있어야 하는 화면을 상상해보겠습니다.
<header>
...내비게이션
</header>
<main>
...본문 내용
</main>
header {
position: sticky;
top: 0;
z-index: 1;
}
main {
position: relative;
z-index: 0;
}
이런 식으로 명시적으로 z-index를 줌으로써 header는 항상 main태그보다 위에 있다고 보장할 수 있게 되는 것입니다.
2. 부모는 local stacking context의 z-index를 이기지 못한다.
.box {
width: 100px;
height: 100px;
}
.first {
z-index: -1;
background: yellow;
position: relative;
top: 50px;
left: 50px;
}
.second {
z-index: 2;
background: tomato;
position: relative;
}
.wrapper {
z-index: 3;
position: relative;
translate: 0 25px;
background-color: green;
}
local stacking context를 생성한 주체가 되는 요소, 즉, z-index가 integer 인 부모 요소는 자신이 생성한 local stacking context 내부의 요소가 자신보다 z-index가 더 낮아도 이길 수 없습니다.
3. stacking context는 생각보다 다양한 상황에서 생성된다.
mdn 쌓임 맥락글에 의하면 opacity속성 backdrop-filter, mix-blend-mode, filter, isolation, perpective 등 생각지도 못한 다양한 속성들이 stacking-context를 형성한다는 점을 확인할 수 있습니다.
당연히 외우는 것은 무리겠지만 의도치 않은 쌓임 현상이 화면에 그려졌다면 이런 ‘stacking-context 때문일수도 있겠구나’ 하고 의심을 하고 해결해 볼 수 있겠습니다.
#css
이 글은 옵시디언(Obsidian)에서 작성되었습니다. 티스토리에서 상대경로 링크는 작동하지 않습니다. 큰 이미지 파일은 업로드되지 않을 수 있습니다.