
z-index가 안 먹을 때
z-index가 작동하지 않는 문제와 stacking context 이해하기

z-index가 작동하지 않는 문제와 stacking context 이해하기
클래스 이름 짓기 지치셨나요? HTML 안에 CSS를 직접 쓰는 기괴한 방식이 왜 전 세계 프론트엔드 표준이 되었는지 파헤쳐봤습니다.

분명히 클래스를 적었는데 화면은 그대로다? 개발자 도구엔 클래스가 있는데 스타일이 없다? Tailwind 실종 사건 수사 일지.

안드로이드는 Xcode보다 낫다고요? Gradle 지옥에 빠져보면 그 말이 쏙 들어갈 겁니다. minSdkVersion 충돌, Multidex 에러, Namespace 변경(Gradle 8.0), JDK 버전 문제, 그리고 의존성 트리 분석까지 완벽하게 해결해 봅니다.

서버에서 잘 오던 데이터가 갑자기 앱을 죽입니다. 'type Null is not a subtype of type String' 에러의 원인과, 안전한 JSON 파싱을 위한 Null Safety 전략을 정리해봤습니다.

모달을 만들었는데 다른 요소 뒤에 숨어버렸습니다:
.modal {
z-index: 9999;
}
.header {
z-index: 10;
}
분명 모달의 z-index가 훨씬 높은데 헤더 뒤에 숨었어요!
제가 가진 오개념은 이거였습니다: "z-index가 높으면 무조건 위에 온다"
하지만 stacking context가 문제였습니다. z-index는 같은 stacking context 안에서만 비교돼요!
이 문제를 이해한 건 이런 비유를 들었을 때였습니다:
"각 방(stacking context)에서 사람들(요소)이 줄을 선다. 방 안에서는 키(z-index)로 순서가 정해지지만, 다른 방의 사람과는 비교 안 된다."해결책은 position을 설정하는 겁니다:
.modal {
position: fixed; /* ✅ position 필수! */
z-index: 9999;
}
z-index는 position: static이 아닌 요소에만 작동합니다!
다음 속성들이 새로운 stacking context를 만듭니다:
/* 1. position + z-index */
.element {
position: relative;
z-index: 1;
}
/* 2. opacity < 1 */
.element {
opacity: 0.99;
}
/* 3. transform */
.element {
transform: translateZ(0);
}
/* 4. filter */
.element {
filter: blur(1px);
}
/* 5. will-change */
.element {
will-change: transform;
}
<div class="parent" style="position: relative; z-index: 1;">
<div class="child" style="position: relative; z-index: 9999;">
Child
</div>
</div>
<div class="other" style="position: relative; z-index: 2;">
Other
</div>
child의 z-index가 9999여도 other 뒤에 숨습니다! parent의 z-index가 1이기 때문이에요.
/* 1. 모달을 body 직속으로 이동 */
ReactDOM.createPortal(<Modal />, document.body)
/* 2. parent의 z-index 제거 */
.parent {
position: relative;
/* z-index 제거 */
}
/* 3. parent의 z-index를 더 높게 */
.parent {
position: relative;
z-index: 10000;
}
최신 CSS 속성인 isolation: isolate를 사용하면 명시적으로 새로운 Stacking Context를 만들 수 있습니다.
transform이나 opacity 같은 부작용 없이 컨텍스트만 분리하고 싶을 때 유용합니다.
.card-wrapper {
isolation: isolate;
/* 내부 요소들의 z-index 계산이 이 안에서만 이루어짐 */
}
사실 Stacking Context는 브라우저가 화면을 그리는 "Painting" 단계와 관련이 깊습니다. 브라우저는 DOM 트리를 해석한 후, Layer Tree를 만듭니다.
display: none 제외)자식 요소(Child)가 아무리 z-index: 9999를 외쳐도, 부모(Parent)가 생성한 레이어 안에 갇혀 있다면, 그 부모보다 낮은 레이어 위에 올라갈 수 없는 것입니다.
이 원리를 이해하면 opacity나 transform이 왜 새로운 Stacking Context를 만드는지 이해할 수 있습니다. (GPU 가속을 위해 별도의 레이어로 분리되기 때문입니다!)
프로젝트가 커지면 z-index: 9999 같은 매직 넘버가 난무하게 됩니다.
Sass Map으로 중앙 관리하세요.
/* _variables.scss */
$z-indexes: (
'toast': 9000,
'modal': 8000,
'dropdown': 7000,
'header': 1000,
'default': 1
);
@function z($key) {
@return map-get($z-indexes, $key);
}
/* 사용 */
.modal {
z-index: z('modal');
}
이렇게 하면 "헤더보다 모달이 높아야지"라는 계층 구조가 한눈에 보입니다.
모달을 Portal로 렌더링했습니다:
import { createPortal } from 'react-dom';
function Modal({ children, isOpen }) {
if (!isOpen) return null;
return createPortal(
<div className="modal-overlay">
<div className="modal-content">
{children}
</div>
</div>,
document.body
);
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
background: rgba(0, 0, 0, 0.5);
}
.modal-content {
position: relative;
z-index: 1001;
}