본문으로 건너뛰기
개발 머꼬
개발 노트CSS
hohyeon.dev18

z-index를 9999로 올려도 모달이 계속 아래에 깔린 이유

  • #CSS
  • #Engineering Note
  • #Layout

문제 발생

모달이 헤더 아래에 깔려서 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) 안에서만 비교됩니다. 서로 다른 맥락에 있는 요소끼리는 숫자를 비교하지 않고, 각자의 맥락이 통째로 어느 순서에 놓이는지로 결정됩니다.

위 코드에서 .cardtransform이 새 쌓임 맥락을 만듭니다. 그러면 .modal의 9999는 .card 안에서만 의미가 있고, 바깥에서 볼 때 모달은 .card라는 한 덩어리의 일부일 뿐입니다. .card가 헤더보다 아래라면 그 안의 모든 것이 아래입니다.

새 쌓임 맥락을 만드는 것들은 생각보다 많습니다.

속성조건
positionabsolute/relativez-indexauto가 아닐 때
positionfixed/sticky는 값과 무관하게 항상
opacity1보다 작을 때
transform, filter, backdrop-filter, perspectivenone이 아닐 때
will-change위 속성 중 하나를 지정했을 때
isolationisolate
containlayout, paint, strict, content
flex/grid 자식z-indexauto가 아닐 때

opacity: 0.99will-change: transform 한 줄이 원인인 경우가 특히 찾기 어렵습니다.

해결 방안

  1. 원인 조상을 찾습니다. 숫자를 올리기 전에 어떤 조상이 맥락을 만들었는지 확인합니다. DevTools의 Layers 패널이나, 조상을 거슬러 올라가며 위 표의 속성을 찾습니다.
  2. 모달은 맥락 밖으로 꺼냅니다. 가장 확실한 해결입니다. React라면 포털로 document.body에 렌더링하면 어떤 카드 안에 있든 영향을 받지 않습니다.
createPortal(<Modal />, document.body)
  1. 최신 방법으로는 top layer를 씁니다. <dialog>showModal()이나 popover는 쌓임 맥락과 무관한 최상위 레이어에 렌더링되므로 z-index 자체가 필요 없어집니다.
  2. 의도적으로 맥락을 만들 때는 isolation: isolate를 씁니다. 컴포넌트 내부의 z-index가 바깥으로 새지 않게 가두는 용도로는 이게 가장 명확합니다 — 부수 효과 없이 맥락만 만듭니다.
  3. z-index는 작은 정수로 관리합니다. 9999는 문제를 해결하지 못하면서 다음 사람에게 10000을 쓰게 만듭니다. 레이어 이름을 토큰으로 정해 두는 편이 낫습니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.