z-index: 99999를 줬는데 요소가 올라오지 않는다. 값을 999999로 올려도 마찬가지다. 이 상황에서 대부분의 개발자는 숫자를 더 키우거나, !important를 붙이거나, 구조를 뒤집기 시작한다. 하지만 문제의 원인은 숫자의 크기가 아니다. 쌓임 맥락(Stacking Context)을 이해하지 못했기 때문이다.
쌓임 맥락이란 무엇인가
브라우저는 HTML 요소들을 2차원 평면에 배치하는 것처럼 보이지만, 실제로는 z축이라는 깊이 차원도 관리한다. 쌓임 맥락은 이 z축 위에서 요소들이 어떤 순서로 겹치는지를 결정하는 독립적인 레이어 그룹이다.
비유하면, 쌓임 맥락은 봉투와 같다. 봉투 안에 여러 장의 종이(자식 요소)가 들어 있고, 종이들 사이의 순서는 봉투 내부에서만 유효하다. 아무리 봉투 안의 종이에 "나는 맨 위야!"라고 적어놔도, 봉투 자체가 다른 봉투 아래에 있으면 절대 위로 올라올 수 없다.
.parent-a {
position: relative;
z-index: 1;
}
.parent-b {
position: relative;
z-index: 2;
}
.child-of-a {
position: absolute;
z-index: 99999;
}
.child-of-a의 z-index가 99999여도, .parent-a의 z-index가 1이므로 .parent-b(z-index: 2) 아래에 머문다. z-index는 같은 쌓임 맥락 안에서만 비교된다.
[!note] 잠깐! 이 용어는? 쌓임 맥락(Stacking Context): z축 위에서 요소들의 렌더링 순서를 결정하는 독립적인 그룹이다. 쌓임 맥락이 생성되면 그 안의 모든 자식 요소는 해당 맥락 내에서만 z-index로 순서가 매겨진다.
쌓임 맥락을 생성하는 조건들
쌓임 맥락은 z-index를 명시적으로 설정했을 때만 생기는 것이 아니다. 의외로 많은 CSS 속성이 암묵적으로 쌓임 맥락을 만든다. 이것이 버그의 주범이다.
.creates-context-1 {
position: relative;
z-index: 1;
}
.creates-context-2 {
opacity: 0.99;
}
.creates-context-3 {
transform: translateZ(0);
}
.creates-context-4 {
isolation: isolate;
}
.creates-context-5 {
will-change: transform;
}
.creates-context-6 {
filter: blur(0);
}
.creates-context-7 {
contain: layout;
}
쌓임 맥락 생성 조건 전체 목록
| 조건 | 설명 |
|---|---|
position: relative/absolute + z-index 값 지정 |
가장 기본적인 생성 방식 |
position: fixed 또는 sticky |
z-index 없어도 생성 |
opacity < 1 |
opacity: 0.99만으로도 생성 |
transform 값 존재 |
transform: none이 아닌 모든 값 |
filter 값 존재 |
filter: blur(0)도 포함 |
will-change |
특정 속성을 명시하면 생성 |
isolation: isolate |
의도적 생성 전용 속성 |
contain: layout 또는 paint |
컨테인먼트 관련 |
Flex/Grid 자식 + z-index 지정 |
position 없이도 작동 |
mix-blend-mode 값 존재 |
normal이 아닌 값 |
-webkit-overflow-scrolling: touch |
모바일 Safari |
clip-path, mask |
클리핑/마스킹 관련 |
여기서 주의할 점은 Flex/Grid 자식 요소다. 일반적으로 z-index가 작동하려면 position이 static이 아니어야 하지만, flex 또는 grid 컨테이너의 자식이라면 position: relative 없이도 z-index가 동작하고 쌓임 맥락이 생성된다.
[!note] 잠깐! 이 용어는? isolation: isolate: 쌓임 맥락을 의도적으로 생성하기 위한 CSS 속성이다. 다른 부수 효과 없이 오직 쌓임 맥락만 만든다.
opacity: 0.99나transform: translateZ(0)같은 핵(hack) 대신 사용하는 정식 방법이다.
쌓임 순서 규칙
같은 쌓임 맥락 안에서 요소들은 다음 순서로 쌓인다(아래부터 위로):
1. 쌓임 맥락의 배경과 테두리
2. z-index가 음수인 자식 (쌓임 맥락 생성된 것)
3. in-flow, non-positioned 블록 요소
4. non-positioned 플로팅 요소
5. in-flow, non-positioned 인라인 요소
6. z-index: 0 또는 z-index: auto인 positioned 요소
7. z-index가 양수인 자식
이 순서를 모르면 "왜 z-index: 0을 준 요소가 z-index를 안 준 요소보다 위에 있지?"라는 혼란에 빠진다. positioned 요소(z-index: auto 포함)는 non-positioned 요소보다 항상 위에 쌓인다.
실전 디버깅 전략
1단계: 쌓임 맥락 트리 파악하기
브라우저 DevTools에서 해당 요소의 부모 체인을 추적하며, 어떤 요소가 쌓임 맥락을 생성하고 있는지 확인한다.
.modal-overlay {
position: fixed;
z-index: 1000;
}
.header {
position: sticky;
top: 0;
z-index: 100;
}
.sidebar {
transform: translateX(0);
}
이 예시에서 .sidebar는 transform 때문에 쌓임 맥락이 생성된다. .sidebar 안의 드롭다운에 아무리 높은 z-index를 줘도, .sidebar의 쌓임 맥락 밖으로 나갈 수 없다.
2단계: isolation: isolate 활용하기
의도적으로 쌓임 맥락의 경계를 만들 때는 isolation: isolate를 사용한다. 이것은 마치 칸막이를 세워서 "여기서부터는 별도 구역이다"라고 선언하는 것과 같다.
.card {
isolation: isolate;
}
.card__badge {
position: absolute;
z-index: 1;
}
.card__image {
position: relative;
z-index: 0;
}
.card에 isolation: isolate를 주면, .card__badge의 z-index는 카드 내부에서만 유효하다. 다른 카드나 외부 요소의 z-index와 충돌하지 않는다.
[!note] 잠깐! 이 용어는? will-change: 브라우저에 "이 속성이 곧 변할 것이니 미리 최적화해두라"고 힌트를 주는 CSS 속성이다. 성능 최적화 목적이지만, 부작용으로 쌓임 맥락을 생성한다. 남용하면 오히려 메모리 사용량이 증가한다.
비교 테이블 — z-index 디버깅 체크리스트
| 증상 | 원인 | 해결책 |
|---|---|---|
| z-index를 올려도 안 올라옴 | 부모의 쌓임 맥락이 낮음 | 부모의 z-index 확인/조정 |
| opacity 애니메이션 후 레이어 꼬임 | opacity < 1이 새 맥락 생성 | animation 종료 후 opacity 제거 |
| transform 적용 후 드롭다운 잘림 | transform이 새 맥락 생성 | isolation: isolate로 구조 분리 |
| 모달이 헤더 아래에 깔림 | 모달과 헤더가 다른 맥락 | 같은 쌓임 맥락에서 비교되도록 구조 변경 |
| Flex 자식의 z-index가 의도대로 안 됨 | Flex 자식은 position 없이 맥락 생성 | 명시적으로 position: relative 추가 |
가장 확실한 해결 전략은 "z-index 숫자를 키우는 것"이 아니라, 쌓임 맥락의 구조를 제어하는 것이다.
마무리
z-index 문제의 99%는 숫자가 작아서가 아니라, 쌓임 맥락의 구조를 파악하지 못해서 발생한다. 봉투 안의 종이가 아무리 높은 번호표를 가져도, 봉투 자체의 순서를 바꾸지 않으면 소용없다. isolation: isolate를 적극 활용해 의도적으로 쌓임 맥락의 경계를 관리하고, DevTools에서 어떤 속성이 암묵적으로 쌓임 맥락을 생성하는지 추적하는 습관을 들이면, z-index와의 전쟁에서 벗어날 수 있다.
참고:
- Smashing Magazine: https://www.smashingmagazine.com/2026/01/unstacking-css-stacking-contexts/
- MDN Stacking Context: https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Positioned_layout/Stacking_context
- Josh W. Comeau: https://www.joshwcomeau.com/css/stacking-contexts/
'노트 > 끄적끄적' 카테고리의 다른 글
| Google SRE는 장애 대응에 AI를 어떻게 쓰는가 — Gemini CLI 실전 활용 (0) | 2026.02.15 |
|---|---|
| Nx 모노레포의 엔진을 Bun으로 교체하기 — 컬리의 마이그레이션 경험기 (0) | 2026.02.15 |
| Steve Yegge의 AI 에이전트 시대 전망: 50% Dial과 드라큘라 효과 (0) | 2026.02.15 |
| React 성능 최적화 4단계: LCP 28초에서 1초로 (0) | 2026.02.15 |
