blog

opacity가 z-index를 망가뜨리는 이유: Stacking Context

Front-end / CSS#CSS#z-index#stacking context#opacity

opacity를 준 요소 안에서 z-index: 999를 줬는데도 다른 요소 아래에 깔리는 경우가 있다. opacity가 z-index를 무효화하는 것이 아니라, 새로운 stacking context를 만들기 때문이다.

stacking context란

z-index는 같은 stacking context 안의 형제끼리만 비교된다. 어떤 요소가 stacking context를 만들면, 그 자식들의 z-index는 아무리 커도 부모의 쌓임 순서 안에서만 유효하다.

stacking context를 만드는 대표적인 조건:

  • opacity가 1 미만
  • position이 relative/absolute이면서 z-index가 auto가 아님
  • position: fixed 또는 sticky
  • transform, filter, perspective, will-change(해당 속성), isolation: isolate, mix-blend-mode 등

예제

<div style="opacity: 0.5; background: red; width: 100px; height: 100px;">
  Box 1
  <div style="position: absolute; z-index: 999; background: green; width: 50px; height: 50px;">
    Inner Box
  </div>
</div>
<div style="position: relative; z-index: 1; background: blue; width: 100px; height: 100px; margin-top: -50px;">
  Box 2
</div>
  • Box 1은 z-index가 없지만 opacity: 0.5 때문에 stacking context가 된다.
  • Inner Box의 z-index: 999는 Box 1 내부에서만 비교된다. 바깥에서 보면 Inner Box는 Box 1 덩어리의 일부일 뿐이다.
  • Box 1 덩어리는 z-index: 1인 Box 2보다 아래에 쌓이므로, Inner Box도 Box 2 아래에 그려진다.

Box 1에서 opacity를 빼면 stacking context가 사라지고, Inner Box의 z-index: 999가 Box 2와 같은 레벨에서 비교되어 위로 올라온다.

해결 방법

  1. 배경만 투명하게: 요소 전체가 아니라 배경만 반투명이면 되는 경우가 많다. background-color: rgb(255 0 0 / 0.5)는 stacking context를 만들지 않는다.
  2. DOM 위치 옮기기: 모달·드롭다운·툴팁처럼 위로 떠야 하는 요소는 stacking context 밖(보통 body 바로 아래)으로 렌더링한다. React의 createPortal, Vue의 <Teleport>가 이 용도다.
  3. 쌓임 순서 재설계: 부모 stacking context 자체의 z-index를 조정한다.

참고로 자식에 position: fixed를 줘도 부모 stacking context를 벗어나지 못한다. 오히려 부모에 transform·filter가 있으면 fixed 요소의 기준점까지 부모로 바뀐다.

요약

  • opacity < 1은 새 stacking context를 만든다.
  • 자식의 z-index는 그 context 안에서만 의미가 있다.
  • 배경 투명도는 색상 알파값으로, 떠야 하는 UI는 Portal/Teleport로 해결한다.