z-index는 겹치는 요소의 앞뒤 쌓임 순서를 제어합니다 — 더 높은 값이 위에 옵니다. 하지만 positioned 요소에서만 작동하며, stacking context 안에서 동작하는데, 이것이 대부분의 z-index 혼란의 원인입니다.
{ : fixed; : ; }
{ : fixed; : ; }
{ : relative; : ; }
z-index는 position: static(기본값)에서는 무시됩니다 — 적용되려면 요소가 relative, absolute, fixed, sticky 중 하나여야 합니다.
stacking context는 자체적으로 완결된 "레이어"입니다. z-index 값은 같은 컨텍스트 안에서만 경쟁합니다 — 자식은 z-index가 아무리 높아도 부모의 쌓임 레벨을 결코 벗어날 수 없습니다.
.parent-a { position: relative; z-index: 1; }
.parent-b { position: relative; z-index: 2; }
.child-of-a { position: absolute; z-index: 9999; }
/* ❌ child-of-a는 여전히 parent-b의 모든 것 아래에 렌더링됨, */
/* parent-a(z-index 1)가 통째로 parent-b(z-index 2) 아래에 있기 때문. */
이것이 전형적인 "내 z-index: 9999 모달이 여전히 무언가 뒤에 있다" 버그입니다 — 모달이 더 낮은 레이어에 있는 부모 안에 갇힌 것입니다.
많은 속성이 조용히 하나를 만들어, 자손을 가둘 수 있습니다.
- position + z-index (auto가 아닌 값)
- opacity가 1 미만
- transform, filter, will-change
- isolation: isolate
- z-index가 있는 flex/grid 아이템
/* 모달을 문서 루트에 렌더링 (가두는 부모 바깥) — "portal" */
.isolated { isolation: isolate; } /* 컨텍스트를 원하는 곳에 의도적으로 생성 */
실무에서는 모달/tooltip을 DOM 최상단에 렌더링(React portal)해 낮은 레이어 부모에 갇히지 않게 합니다.
stacking context는 가장 당혹스러운 z-index 버그를 설명합니다: 거대한 z-index가 조상이 더 낮은 레이어에 있어서 여전히 지는 경우.
opacity, transform, positioned-z-index가 컨텍스트를 만든다는 것과 자식이 그것을 벗어날 수 없다는 것을 알면 오버레이/dropdown 순서를 디버깅하고 DOM(portal)을 올바르게 구성할 수 있습니다.