본문으로 건너뛰기

© 2026 Molayo

Smashing헤드라인2026. 05. 20. 01:34

CSS 쌓임 맥락 (Stacking Contexts) 해체하기

요약

CSS의 쌓임 맥락(Stacking Context) 개념을 통해 요소들이 화면상에서 겹쳐지는 원리를 설명합니다. z-index 값이 아무리 높더라도 부모 요소가 형성한 쌓임 맥락의 제약을 받기 때문에 의도한 대로 요소가 배치되지 않을 수 있음을 강조합니다.

핵심 포인트

  • z-index는 해당 요소가 속한 쌓임 맥락 내에서만 유효합니다.
  • 부모 요소가 새로운 쌓임 맥락을 형성하면, 자식 요소의 z-index는 부모의 순서에 종속됩니다.
  • 애니메이션, 변형(Transform), 불투명도(Opacity) 등의 속성은 새로운 쌓임 맥락을 생성할 수 있습니다.
  • 쌓임 맥락은 브라우저가 렌더링 효율성을 높이기 위해 요소를 독립적인 레이어로 관리하는 방식입니다.

CSS에서 특정 요소에 z-index: 99999를 설정했는데도 다른 요소들보다 위에 나타나지 않았던 적이 있나요? 모든 요소가 더 낮은 값을 가지고 있거나 아예 설정되어 있지 않다고 가정할 때, 그 정도로 큰 값이라면 해당 요소가 시각적으로 다른 무엇보다도 쉽게 위에 위치해야 합니다.

웹페이지는 보통 2차원 공간으로 표현됩니다. 하지만 특정 CSS 속성을 적용함으로써 깊이감을 전달하기 위한 가상의 z축 평면이 도입됩니다. 이 평면은 화면에 수직이며, 사용자는 이를 통해 요소들이 서로 겹쳐져 있는 순서를 인지합니다. 가상의 z축, 즉 사용자가 인지하는 쌓여 있는 요소들의 이면에 있는 개념은, 이를 생성하는 CSS 속성들이 결합하여 우리가 **쌓임 맥락 (stacking context)**이라고 부르는 것을 형성한다는 것입니다.

우리는 웹페이지에서 요소들이 어떻게 "쌓이는지", 무엇이 쌓임 순서를 제어하는지

이해하려고 노력해보세요: 한 장의 종이(즉, 자식 요소)가 폴더(즉, 부모의 쌓임 맥락) 안에 들어가면, 그 폴더를 벗어나거나 다른 폴더에 있는 종이들 사이에 놓일 수 없습니다. 이 종이의 z-index는 이제 자신의 폴더 안에서만 관련성이 있습니다.

아래 그림에서 Paper B는 Folder B의 쌓임 맥락 내부에 있으므로, 해당 폴더의 다른 종이들과만 순서를 정할 수 있습니다.

당신의 책상 위에 두 개의 폴더가 있다고 상상해 보세요:

<div class="folder-a">Folder A</div>
<div class="folder-b">Folder B</div>
.folder-a { z-index: 1; }
.folder-b { z-index: 2; }

마크업을 조금 업데이트해 보겠습니다. Folder A 안에는 특별한 페이지가 있고, z-index: 9999입니다.

. Folder B 안에는 평범한 페이지가 있고, z-index: 5입니다.

.

<div class="folder-a">
<div class="special-page">Special Page</div>
</div>
...
.special-page { z-index: 9999; }
.plain-page { z-index: 5; }

어떤 페이지가 위에 있을까요?

Folder B의 .plain-page입니다. 브라우저는 자식 종이들은 무시하고 두 폴더를 먼저 쌓습니다. 브라우저는 Folder B (z-index: 2)를 보고 Folder A (z-index: 1) 위에 배치합니다. 왜냐하면 우리는 2가 1보다 크다는 것을 알기 때문입니다. 한편, z-index가 가장 높은 값으로 설정된 .special-page 페이지는 스택의 맨 아래에 위치합니다.

쌓임 맥락(Stacking contexts)은 또한 중첩될 수 있습니다 (폴더 안에 폴더). 이는

, 브라우저에게 *“이봐, 이 요소는 움직이거나, 회전하거나, 흐려질 수도 있으니 준비해둬!”*라고 말하는 것과 같습니다.

이러한 속성들을 사용하면 브라우저는 렌더링을 더 효율적으로 관리하기 위해 새로운 쌓임 맥락 (Stacking context)을 생성합니다. 이를 통해 브라우저는 애니메이션 (Animations), 변형 (Transforms), 시각 효과 (Visual effects)를 독립적으로 처리할 수 있으며, 이 요소들이 페이지의 나머지 부분과 어떻게 상호작용하는지 다시 계산해야 하는 필요성을 줄여줍니다. 브라우저가 *“이 폴더 안의 내용이 바뀔 때마다 책상 전체를 다시 정리할 필요가 없도록, 이 폴더는 따로 관리할게.”*라고 말하는 것으로 생각하면 됩니다.

하지만 부작용도 있습니다. 브라우저가 특정 요소를 자체적인 레이어 (Layer)로 들어 올리면, 그 내부의 모든 것을 “평탄화 (Flatten)”하여 새로운 쌓임 맥락을 생성해야 합니다. 이는 폴더를 책상에서 따로 꺼내어 처리하는 것과 같습니다. 폴더 안의 모든 것이 그룹화되며, 브라우저는 이제 무엇이 무엇 위에 놓일지 결정할 때 이를 하나의 단위로 취급합니다.

따라서 transformopacity 속성이 요소들이 시각적으로 쌓이는 방식에 영향을 주지 않는 것처럼 보일지라도, 실제로는 영향을 미치며 이는 성능 최적화 (Performance optimisation)를 위한 것입니다. 몇몇 다른 CSS 속성들도 유사한 이유로 쌓임 맥락을 생성할 수 있습니다. 더 깊이 파고들고 싶다면 MDN에서 전체 목록을 제공합니다. 쌓임 맥락을 생성하는 속성은 꽤 많으며, 이는 자신도 모르게 의도치 않게 쌓임 맥락을 만드는 것이 얼마나 쉬운지를 잘 보여줍니다.

“언스태킹 (Unstacking)” 문제

쌓임 문제는 여러 가지 이유로 발생할 수 있지만, 그중에서도 특히 흔한 것들이 있습니다. 모달 (Modal) 컴포넌트는 전형적인 사례입니다. 모달은 모든 다른 요소들보다 상위 레이어에서 “열림” 상태로 토글되어야 하며, “닫힐” 때는 상위 레이어에서 제거되어야 하기 때문입니다.

우리 모두는 모달을 열었는데 어떤 이유에서인지 화면에 나타나지 않는 상황을 한 번쯤은 겪어보았을 것이라 확신합니다. 모달이 제대로 열리지 않은 것이 아니라, 쌓임 맥락의 더 낮은 레이어에 있어서 시야에서 벗어난 것입니다.

이런 상황을 겪으면 다음과 같이 설정했음에도 불구하고 “도대체 왜?”라는 의문이 들게 됩니다:

.overlay {
position: fixed; /* 쌓임 맥락 (stacking context)을 생성합니다 */
z-index: 1; /* 요소를 다른 모든 것보다 높은 레이어에 배치합니다 */
...

이 코드는 올바르게 보이지만, 모달 트리거 (modal trigger)를 포함하는 부모 요소가 z-index: 1로 설정된 또 다른 부모 요소의 자식 요소라면, 기술적으로 모달은 메인 폴더에 의해 가려지는 하위 레이어에 위치하게 됩니다. 이 구체적인 시나리오와 다른 몇 가지 흔한 쌓임 맥락 (stacking-context)의 함정들을 살펴보겠습니다. 이를 통해 의도치 않게 쌓임 맥락을 생성하는 것이 얼마나 쉬운지뿐만 아니라, 이를 어떻게 잘못 관리하게 되는지도 알게 될 것입니다. 또한, 관리 가능한 상태로 되돌리는 방법은 상황에 따라 다릅니다.

시나리오 1: 갇혀버린 모달 (The Trapped Modal)

헤더에 있는 “모달 열기” 버튼을 클릭하면, 오버레이 (overlay)와 모달이 메인 콘텐츠 뒤에 나타나는 것을 확인할 수 있습니다. 이는 모달이 헤더 컨테이너의 자식이기 때문인데, 이 헤더 컨테이너는 메인 컨테이너 (z-index2)보다 낮은 쌓임 맥락 순서 (z-index: 1)를 가지고 있습니다. 모달 오버레이와 모달이 각각 99989999z-index 값을 가지고 있음에도 불구하고, z-index: 2를 가진 메인 컨테이너가 여전히 그들 바로 위에 놓이게 됩니다.

시나리오 2: 잠겨버린 드롭다운 (The Submerged Dropdown)

여기서는 첫 번째 시나리오와 유사한 문제가 발생합니다. “services” 링크에 마우스를 올리면 드롭다운이 나타나지만, 메인 컨테이너 뒤에 나타납니다. 드롭다운이 나타나는 것을 충분히 볼 수 있으면서도 메인 컨테이너 바로 뒤에 머물도록 하기 위해, 의도적으로 메인 컨테이너의 margin-top20px로 설정했습니다. 이것은 프론트엔드 개발자들이 쌓임 맥락 (context stacking)으로 인해 겪는 또 다른 흔한 문제입니다. 첫 번째 시나리오와 유사하지만, 이를 해결하는 또 다른 접근 방식이 있으며 이는 곧 살펴볼 예정입니다.

시나리오 3: 잘려나간 툴팁 (The Clipped Tooltip)

이제 이것은 흥미로운 사례입니다. 이것은 어떤 요소가 더 높은 z-index를 가졌느냐의 문제가 아닙니다. 바로 overflow: hidden에 관한 문제입니다.

설계된 대로 작동하고 있는 것입니다. 즉, 해당 콘텐츠가 z-index: 1000을 가지고 있더라도 시각적으로 컨테이너를 벗어나는 것을 방지하고 있는 것입니다.

overflow: hiddenz-index: 1000을 막을 수 있을 거라고 누가 생각이나 했을까요? 네, 위 Codepen에서 볼 수 있듯이 실제로 막아버렸습니다.

개발자들이 z-index를 너무 신뢰한 나머지, 어떤 가려짐 문제라도 z-index가 해결해 줄 것이라 기대하는 것 같습니다. 하지만 현실은 그렇지 않습니다. z-index가 강력하지 않다는 뜻이 아니라, 요소를 최상단으로 밀어 올리는 능력은 다른 요소들에 의해 결정된다는 뜻입니다.

해당 요소에 z-index를 무작정 적용하기 전에 기억하세요. 이것이 현재의 난관은 해결해 줄 수 있을지 몰라도, z-index: infinity로도 해결할 수 없는 더 큰 난관에 빠뜨릴 수도 있습니다.

문제를 해결하려고 시도하기 전에, 먼저 문제를 이해해 봅시다.

갇힌 레이어 식별하기 (Identifying The Trapped Layer)

위에서 언급한 것과 같은 문제에 직면했을 때, 해당 요소에 문제가 생긴 것이 아니라 조상 요소가 죄를 지었고 자식 요소가 그 대가를 치르고 있다는 사실을 아는 것이 도움이 됩니다. 비종교적인 영어 표현으로 말하자면, 가려진 요소 자체가 문제가 아니라, 조상 요소가 더 낮은 수준의 쌓임 맥락 (Stacking Context)을 생성했기 때문에, 해당 자식 요소들이 더 높은 수준의 쌓임 맥락을 가진 부모의 자식들보다 아래에 놓이게 된 것입니다.

그 부모 요소를 추적하고 찾는 좋은 방법은 브라우저의 개발자 도구 (Devtools)로 들어가 요소를 검사하며 위로 올라가는 것입니다. 각 부모 레벨을 확인하며 어떤 요소가 쌓임 맥락 (Stacking Context)을 트리거하는 속성을 가지고 있는지 확인하고, 형제 요소들과 비교했을 때의 순서상 위치를 파악하십시오. 단계별로 진행할 수 있도록 체크리스트를 만들어 보겠습니다.

디버깅 체크리스트

문제 요소를 검사합니다 (Inspect the Problem Element).

숨겨진 요소(모달, 드롭다운 메뉴, 툴팁 등)를 마우스 오른쪽 버튼으로 클릭하고 "검사(Inspect)"를 클릭합니다.

스타일을 확인합니다 (Check its Styles).

"Styles" 또는 "Computed" 창에서 해당 요소가 예상대로 높은 z-index (예: z-index: 9999;)를 가지고 있는지 확인합니다.

DOM 트리로 올라갑니다 (Climb the DOM Tree).

“Elements” 패널에서 해당 요소의 직계 부모(immediate parent)를 확인합니다. 부모 요소를 클릭하세요.

부모의 스타일 조사하기 (Investigate the Parent’s Styles).

“Styles” 창에서 부모의 CSS를 확인합니다. 이제 새로운 쌓임 맥락 (Stacking Context)을 생성하는 속성을 찾아내야 합니다. 위치 지정 (positioning), 시각적 효과 (visual effects), 그리고 컨테인먼트 (containment)와 관련된 모든 속성을 살펴보세요.

반복하기 (Repeat).

만약 직계 부모가 깨끗하다면, 그 부모(해당 요소의 조부모)를 클릭합니다. 4단계를 반복하세요. 범인을 찾을 때까지 한 번에 한 단계씩 DOM 트리를 계속 올라갑니다.

이제 이 체크리스트를 세 가지 시나리오에 적용해 보겠습니다.

문제 1: 갇혀버린 모달 (The Trapped Modal)

검사 (Inspect): .modal-content를 검사합니다.

스타일 확인 (Check Styles): z-index: 9999가 보입니다. 이것은 문제가 아닙니다.

올라가기 (Climb): 부모인 .modal-container를 확인합니다. 여기에는 가두는 속성이 없습니다.

다시 올라가기 (Climb Again): 그 부모인 .header를 확인합니다.

조사 (Investigate): .header의 스타일을 확인하니 범인을 찾았습니다: position: absolutez-index: 1입니다. 이 요소가 쌓임 맥락 (Stacking Context)을 생성하고 있습니다. 함정을 발견한 것입니다! 모달의 z-index: 9999z-index: 1이라는 폴더 안에 “갇혀” 있는 상태입니다.

문제 2: 잠겨버린 드롭다운 (The Submerged Dropdown)

검사 (Inspect): .dropdown-menu를 검사합니다.

스타일 확인 (Check Styles): z-index: 100이 보입니다.

올라가기 (Climb): 부모인 li를 확인하고, 그다음 부모인 ul을 확인한 뒤, 그다음 부모인 .navbar를 확인합니다.

조사 (Investigate): .navbarposition: relativez-index: 1을 가지고 있음을 발견했습니다. 이것이 쌓임 맥락 A (Stacking Context A)를 생성합니다.

형제 요소 분석 (Analyse Siblings): 이것이 전부가 아닙니다. 왜 콘텐츠 아래에 있는 걸까요? 이제 .navbar의 형제 요소인 .content를 검사합니다. .contentposition: relativez-index: 2 (쌓임 맥락 B)를 가지고 있음을 발견합니다. 브라우저는 “폴더”를 쌓고 있습니다: .content (2)를 .navbar (1) 위에 쌓는 것입니다. 근본 원인을 찾아냈습니다.

문제 3: 잘려나간 툴팁 (The Clipped Tooltip)

검사 (Inspect): .tooltip을 검사합니다.

스타일 확인 (Check Styles): z-index: 1000이 보입니다.

올라가기 (Climb): 부모인 .tooltip-trigger를 확인합니다. 문제는 없습니다.

다시 올라가기 (Climb Again): 부모인 .card-container를 확인합니다.

조사 (Investigate): 스타일을 스캔해 보니 범인을 찾았습니다: overflow: hidden입니다. 이것은 일종의 특수한 함정입니다. z-index 값과 관계없이, 경계 외부로 렌더링되려는 모든 자식 요소를 잘라냅니다 (clip).

고급 도구 (Advanced Tooling)

DOM 트리를 타고 올라가는 방식이 효과적이긴 하지만, 속도가 느릴 수 있습니다. 작업 속도를 높여주는 도구들을 소개합니다.

DevTools 3D View

Microsoft Edge (“More Tools” 메뉴 내)나 Firefox (“Inspector” 탭 내)와 같은 일부 브라우저에는 “3D View” 또는 “Layers” 패널이 포함되어 있습니다. 이 도구는 매우 유용합니다. 웹페이지를 서로 다른 레이어(layers)로 시각적으로 분해하여, 쌓임 맥락 (stacking contexts)이 어떻게 그룹화되어 있는지 정확하게 보여줍니다.

이를 통해 모달 (modal)이 낮은 수준의 레이어에 갇혀 있는 것을 즉시 확인하고, 그 부모 요소를 식별할 수 있습니다.

브라우저 확장 프로그램 (Browser Extensions)

숙련된 개발자들은 도움을 주기 위한 확장 프로그램을 만들어 두었습니다. 이 “CSS Stacking Context Inspector” Chrome 확장 프로그램과 같은 도구들은 추가적인 z-index

AI 자동 생성 콘텐츠

본 콘텐츠는 Smashing Magazine의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0