z-index를 9999로 올려도 모달이 계속 아래에 깔린 이유
문제 발생
모달이 헤더 아래에 깔려서 z-index를 9999까지 올렸는데도 그대로였습니다. 헤더의 z-index는 겨우 40이었습니다.
.header { position: sticky; z-index: 40; }
.card { transform: translateY(0); } /* 애니메이션용 */
.card .modal { position: fixed; z-index: 9999; } /* 그래도 헤더 아래 */원인 분석
z-index 값은 같은 쌓임 맥락(stacking context) 안에서만 비교됩니다. 서로 다른 맥락에 있는 요소끼리는 숫자를 비교하지 않고, 각자의 맥락이 통째로 어느 순서에 놓이는지로 결정됩니다.
위 코드에서 .card의 transform이 새 쌓임 맥락을 만듭니다. 그러면 .modal의 9999는 .card 안에서만 의미가 있고, 바깥에서 볼 때 모달은 .card라는 한 덩어리의 일부일 뿐입니다. .card가 헤더보다 아래라면 그 안의 모든 것이 아래입니다.
새 쌓임 맥락을 만드는 것들은 생각보다 많습니다.
| 속성 | 조건 |
|---|---|
position | absolute/relative에 z-index가 auto가 아닐 때 |
position | fixed/sticky는 값과 무관하게 항상 |
opacity | 1보다 작을 때 |
transform, filter, backdrop-filter, perspective | none이 아닐 때 |
will-change | 위 속성 중 하나를 지정했을 때 |
isolation | isolate |
contain | layout, paint, strict, content |
| flex/grid 자식 | z-index가 auto가 아닐 때 |
opacity: 0.99나 will-change: transform 한 줄이 원인인 경우가 특히 찾기 어렵습니다.
해결 방안
- 원인 조상을 찾습니다. 숫자를 올리기 전에 어떤 조상이 맥락을 만들었는지 확인합니다. DevTools의 Layers 패널이나, 조상을 거슬러 올라가며 위 표의 속성을 찾습니다.
- 모달은 맥락 밖으로 꺼냅니다. 가장 확실한 해결입니다. React라면 포털로
document.body에 렌더링하면 어떤 카드 안에 있든 영향을 받지 않습니다.
createPortal(<Modal />, document.body)- 최신 방법으로는 top layer를 씁니다.
<dialog>의showModal()이나 popover는 쌓임 맥락과 무관한 최상위 레이어에 렌더링되므로z-index자체가 필요 없어집니다. - 의도적으로 맥락을 만들 때는
isolation: isolate를 씁니다. 컴포넌트 내부의z-index가 바깥으로 새지 않게 가두는 용도로는 이게 가장 명확합니다 — 부수 효과 없이 맥락만 만듭니다. - z-index는 작은 정수로 관리합니다. 9999는 문제를 해결하지 못하면서 다음 사람에게 10000을 쓰게 만듭니다. 레이어 이름을 토큰으로 정해 두는 편이 낫습니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.