z-index란 무엇일까요? z-index는 HTML 요소를 서로 겹쳐서 층층이 쌓아 올릴 수 있게 해주는 CSS 속성입니다. 처음에는 아주 간단해 보이지만, 사실 그 단순함은 함정입니다.
직관에 어긋나는 기묘한 규칙들이 있어서, z-index를 999999로 설정했는데도 원하는 대로 동작하지 않는 경우가 종종 발생합니다.
이 글에서는 z-index가 의도대로 작동하지 않는 가장 흔한 네 가지 원인을 자세히 설명합니다. CSS를 활용해 요소를 맨 앞으로 가져오거나, 다른 요소 뒤로 보내는 방법까지 함께 배워보세요.
1. 같은 쌓임 맥락(stacking context)에서는 마크업 순서대로 표시된다
첫 번째 예제는 3개의 주요 요소로 구성된 비교적 간단한 레이아웃입니다.
- 고양이 이미지 하나
- 텍스트가 담긴 흰색 블록
- 같은 고양이의 또 다른 이미지
HTML 마크업은 다음과 같습니다.
<div class="cat-top"></div>
<div class="content__block">
Meow meow meow...
</div>
<div class="cat-bottom"></div>
이 레이아웃에서 우리가 원하는 것은 흰색 텍스트 블록이 두 고양이 이미지 위에 모두 올라오는 모습입니다.
이를 위해 두 고양이 이미지에 음수 마진(negative margin)을 적용해 흰색 블록과 살짝 겹치도록 만들었습니다.
.cat-top {
margin-bottom: -110px;
}
.cat-bottom {
float: right;
margin-top: -120px;
}
그런데 결과는 어떨까요? 첫 번째 고양이는 의도대로 흰색 콘텐츠 블록 아래에 위치했습니다. 하지만 두 번째 고양이 이미지는 블록 위에 올라와 버렸습니다!
왜 이런 일이 생기는 걸까요?
원인은 웹페이지의 자연스러운 쌓임 순서(natural stacking order) 때문입니다. 이 규칙들은 어떤 요소가 위에 오고 어떤 요소가 아래에 올지를 결정합니다.
요소에 z-index가 설정되어 있지 않더라도, 무엇이 위에 올지는 일정한 논리가 있습니다.
우리 예제에서는 어떤 요소에도 z-index 값이 없습니다. 따라서 쌓임 순서는 등장 순서에 따라 결정됩니다. 이 규칙에 따르면, 마크업에서 나중에 나온 요소가 먼저 나온 요소보다 위에 표시됩니다.
(쌓임 순서 규칙에 대한 자세한 내용은 Mozilla Developer Network 문서에서 확인할 수 있습니다.)
고양이와 흰색 블록 예제도 바로 이 규칙을 따르고 있습니다. 첫 번째 고양이가 흰색 블록 아래에 있고, 흰색 블록이 두 번째 고양이 아래에 있는 이유가 바로 이것입니다.
자, 그렇다면 CSS를 어떻게 수정해야 두 번째 고양이를 흰색 블록 아래로 보낼 수 있을까요?
두 번째 원인을 살펴보겠습니다.
2. 요소에 position 속성이 설정되어 있지 않다
쌓임 순서를 결정하는 또 다른 규칙은 해당 요소에 position이 설정되어 있는지 여부입니다.
요소에 position을 지정하려면 static이 아닌 값, 예를 들어 relative나 absolute처럼 CSS position 속성을 추가하면 됩니다.
이 규칙에 따르면, position이 설정된 요소는 설정되지 않은 요소보다 위에 표시됩니다.
따라서 흰색 블록에 position: relative를 적용하고, 두 고양이 요소는 position 없이 그대로 두면, 쌓임 순서상 흰색 블록이 고양이들 위에 오게 됩니다.
결과는 다음과 같습니다. 위의 CodePen에서 직접 실험해 볼 수도 있습니다.
성공입니다!
이제 다음으로 할 일은 transform 속성을 사용해 아래쪽 고양이를 거꾸로 뒤집는 것입니다. 그러면 두 고양이 모두 흰색 블록 아래에 위치하고 머리만 살짝 내밀게 될 것입니다.
그런데 여기서 또 다른 z-index 관련 혼란이 발생할 수 있습니다. 문제와 해결책은 다음 파트에서 다루겠습니다.
3. opacity나 transform 같은 CSS 속성이 새로운 쌓임 맥락을 만든다
앞서 언급했듯이, 아래쪽 고양이를 거꾸로 뒤집으려고 합니다. 이를 위해 transform: rotate(180deg)를 추가해 보겠습니다.
.cat-bottom {
float: right;
margin-top: -120px;
transform: rotate(180deg);
}
그런데 이렇게 하니 아래쪽 고양이가 다시 흰색 블록 위로 올라와 버렸습니다!
대체 무슨 일이 일어난 걸까요??
자주 마주치는 상황은 아니지만, 쌓임 순서의 또 다른 특징은 transform이나 opacity 같은 일부 CSS 속성이 해당 요소를 자체적인 새로운 쌓임 맥락(stacking context)에 넣는다는 점입니다.
즉, .cat-bottom 요소에 transform을 추가하면 마치 z-index: 0을 가진 것처럼 동작하게 됩니다. 심지어 position이나 z-index를 전혀 설정하지 않았는데도 말입니다! (W3.org에 opacity 속성이 이렇게 동작하는 원리에 대한 다소 방대하지만 유익한 문서가 있습니다.)
기억하세요. 우리는 흰색 블록에 z-index 값을 추가한 적이 없습니다. position: relative만 추가했을 뿐입니다. 하지만 그것만으로도 position이 없는 고양이들 위에 흰색 블록을 올릴 수 있었습니다.
그런데 .cat-bottom 요소는 transform이 적용되면서 마치 position: relative에 z-index: 0을 가진 것처럼 동작하기 때문에, 흰색 블록 위로 올라가 버린 것입니다.
해결책은 최소한 흰색 블록에는 position: relative와 명시적인 z-index 값을 설정하는 것입니다. 더 안전하게 하려면 고양이 요소에도 position: relative와 더 낮은 z-index를 지정하면 됩니다.
.content__block {
position: relative;
z-index: 2;
}
.cat-top, .cat-bottom {
position: relative;
z-index: 1;
}
개인적으로는 이 방법만으로도 대부분의 기본적인 z-index 문제가 해결된다고 생각합니다.
이제 z-index가 작동하지 않는 마지막 원인으로 넘어가 보겠습니다. 이번 것은 조금 더 복잡한데, 부모 요소와 자식 요소의 관계가 관련되어 있기 때문입니다.
4. 부모의 z-index 수준 때문에 요소가 낮은 쌓임 맥락에 갇혀 있다
이번 코드 예제를 살펴보겠습니다.
여기 있는 것은 일반적인 콘텐츠가 담긴 간단한 웹페이지이고, 콘텐츠 위에 "Send Feedback(피드백 보내기)"이라고 적힌 분홍색 사이드 탭이 있습니다.
그리고 고양이 사진을 클릭하면 반투명 회색 배경 오버레이가 있는 모달 창이 열립니다.
그런데 모달 창이 열려 있는 상태에서도 사이드 탭이 여전히 회색 오버레이 위에 표시됩니다. 우리가 원하는 것은 오버레이가 사이드 탭을 포함한 모든 요소 위에 표시되는 것입니다.
문제가 되는 요소들의 CSS를 살펴보겠습니다.
.content {
position: relative;
z-index: 1;
}
.modal {
position: fixed;
z-index: 100;
}
.side-tab {
position: fixed;
z-index: 5;
}
모든 요소에 position이 설정되어 있고, 사이드 탭은 z-index: 5를 가져 z-index: 1인 콘텐츠 요소 위에 위치합니다.
그리고 모달은 z-index: 100을 가지고 있으므로 당연히 z-index: 5인 사이드 탭 위에 있어야 합니다. 하지만 실제로는 모달 오버레이가 사이드 탭 아래에 있습니다.
왜 이런 일이 벌어지는 걸까요?
앞서 우리는 position 설정 여부, 마크업 순서 등 쌓임 맥락에 영향을 주는 몇 가지 요소를 살펴봤습니다.
그런데 쌓임 맥락의 또 다른 중요한 특징은, 자식 요소는 부모의 쌓임 맥락 안에 제한된다는 점입니다.
세 요소를 좀 더 자세히 들여다보겠습니다.
마크업은 다음과 같습니다.
<section class="content">
<div class="modal"></div>
</section>
<div class="side-tab"></div>
마크업을 보면 content와 side-tab은 형제(sibling) 요소입니다. 즉, 마크업상 같은 계층에 존재합니다(z-index 수준과는 다른 개념입니다). 그리고 modal은 content 요소의 자식 요소입니다.
모달이 content 요소 내부에 있기 때문에, 모달의 z-index: 100은 부모인 content 요소 내부에서만 효과가 있습니다. 예를 들어 모달과 형제 관계인 다른 자식 요소들이 있다면, 그들의 z-index 값에 따라 서로 위아래로 배치될 것입니다.
하지만 그 자식 요소들의 z-index 값은 부모 밖에서는 아무 의미가 없습니다. 부모인 content 요소의 z-index가 1로 설정되어 있기 때문입니다.
따라서 모달을 포함한 자식 요소들은 그 z-index 수준을 벗어날 수 없습니다.
(약간 씁쓸한 비유로 기억할 수 있습니다. 자식은 부모에게 제한을 받으며, 그들로부터 벗어날 수 없다는 것이죠.)
이 문제에는 몇 가지 해결 방법이 있습니다.
해결 방법 1: 모달을 content 부모 밖으로 옮겨 페이지의 메인 쌓임 맥락에 넣기
수정된 마크업은 다음과 같습니다.
<section class="content"></section>
<div class="modal"></div>
<div class="side-tab"></div>
이제 modal 요소는 나머지 두 요소와 형제 관계가 되었습니다. 세 요소가 모두 같은 쌓임 맥락에 놓이게 되므로, 각각의 z-index 수준이 서로에게 영향을 미치게 됩니다.
이 새로운 쌓임 맥락에서 요소들은 위에서부터 아래로 다음 순서대로 표시됩니다.
- modal (
z-index: 100) - side tab (
z-index: 5) - content (
z-index: 1)
해결 방법 2: content에서 position을 제거하여 모달의 z-index 제한 해제하기
마크업을 변경하고 싶지 않거나 변경할 수 없다면, content 요소에서 position 설정을 제거하는 방법으로 문제를 해결할 수 있습니다.
.content {
// position 미설정
}
.modal {
position: absolute;
z-index: 100;
}
.side-tab {
position: absolute;
z-index: 5;
}
content 요소가 이제 position이 없는 상태가 되었으므로, 더 이상 모달의 z-index 값을 제한하지 않습니다. 따라서 열린 모달은 더 높은 z-index: 100 덕분에 사이드 탭 요소 위에 표시됩니다.
물론 이 방법도 동작하지만, 저라면 첫 번째 해결책을 선택하겠습니다.
나중에 어떤 이유로든 content 요소에 position을 지정해야 하는 상황이 오면, 다시 모달의 쌓임 순서가 제한되기 때문입니다.
정리하며
이 튜토리얼이 도움이 되었기를 바랍니다! 정리하자면, 대부분의 z-index 문제는 다음 두 가지 지침만 지켜도 해결할 수 있습니다.
- 요소에 position이 설정되어 있고, z-index 값이 올바른 순서로 지정되어 있는지 확인하세요.
- 부모 요소가 자식 요소의
z-index수준을 제한하고 있지 않은지 확인하세요.
참고 자료:
- W3.org: Transparency – the 'opacity' property
- W3.org: Specifying the stack level
- Mozilla Developer Network: The Stacking Context
- PhilipWalton.com: What No One Told You About Z-index
- Smashing Magazine: The Z-index CSS Property