CSS z-index가 안 먹힐 때: stacking context와 부모 층 디버깅

2026.09.12·수정 2026.09.14·약 28분·작성: 해비·블로그 소개

메뉴의 z-index를 올렸는데도 옆 카드 아래에 가려진다면 숫자부터 더 크게 만들기보다 부모의 층 구조를 확인해 보세요. z-index는 페이지 모든 요소를 하나의 줄에 세우는 전역 순위가 아닙니다. 이 글에서는 자식 배지의 값은 그대로 두고 부모의 값만 바꾸어 결과를 비교합니다. 눈에 보이는 겹침과 DOM 계층을 연결하는 것이 실습의 목표입니다.

stacking context는 함께 비교되는 층의 묶음입니다

쌓임 맥락 안의 요소는 그 맥락 내부에서 순서를 정합니다. 바깥에서 보면 하나의 묶음으로 취급되므로 안쪽 자식이 높은 값을 갖더라도 부모가 속한 순서를 혼자 넘어설 수는 없습니다. 실습에서 부모 A의 값은 1, 부모 B의 값은 2입니다. A 안의 배지는 10이지만 A 전체가 B보다 아래에 있으므로 겹친 부분은 B가 가립니다.

여기서 10은 큰 숫자로 문제를 해결하라는 뜻이 아닙니다. 자식 값이 형제 부모보다 커도 결과가 달라지지 않는다는 사실을 드러내기 위한 비교용 값입니다. 먼저 A와 B가 같은 단계에서 비교되고, 그다음 A 안에서 배지의 순서를 생각합니다. DOM에서 더 깊다는 이유만으로 항상 위에 놓이는 것도 아닙니다. 실제로 어떤 요소가 쌓임 맥락을 만들었는지가 중요합니다.

이 예제에서 맥락이 만들어지는 지점

부모 A와 B는 position: absolute와 auto가 아닌 z-index를 함께 가지므로 각각의 쌓임 맥락을 만듭니다. 바깥 stage에는 isolation: isolate를 주어 두 실험의 층 비교가 서로 섞이지 않게 했습니다. 이 선언은 내용을 잘라내는 overflow 규칙과 다릅니다. 예제는 배지를 부모 A의 오른쪽 밖으로 일부 내보내지만 실험판 내부에 들어오도록 크기를 잡았습니다.

프로젝트에서는 위치 지정 외에도 opacity가 1보다 작은 경우나 transform이 적용된 경우처럼 맥락을 만드는 다른 조건을 만날 수 있습니다. Flex 또는 Grid 항목에서는 위치 지정 없이도 auto가 아닌 z-index가 영향을 줄 수 있으므로 무조건 position을 추가하는 처방은 정확하지 않습니다. 개발자 도구에서 선택한 요소와 조상들의 계산된 스타일을 살피고, 어떤 선언이 새 맥락을 만들었는지 원인별로 확인하세요.

실제로 바꿀 대상은 부모 A입니다

수정 영역은 .fixed-stage .panel-a의 z-index를 3으로 바꿉니다. 자식 배지의 10은 그대로입니다. A가 B의 2보다 위로 이동하므로 배지도 함께 드러납니다. 이 변화는 배지만 끌어올리는 것이 아니라 A 전체의 순서를 바꿉니다. 따라서 실제 화면에서 부모를 올릴 때는 그 안의 다른 버튼, 배경, 안내문까지 함께 위로 올라와도 되는지 확인해야 합니다.

부모 전체를 올리는 것이 의도와 다르다면 다른 선택이 필요합니다. 불필요한 맥락 생성 속성을 제거할 수 있는지, 떠 있는 UI를 더 적절한 공통 조상 아래로 옮겨야 하는지 검토하세요. 다만 DOM 이동은 스타일 상속과 이벤트, 포커스 처리에도 영향을 줄 수 있습니다. 이 실습은 수정 지점을 명확하게 보이기 위해 부모의 순서 변경 한 가지만 적용합니다. 메뉴나 대화상자를 완성한 코드로 해석하지 마세요.

잘림 문제는 z-index 문제와 별개입니다

요소가 뒤에 가려지는 것과 경계에서 잘리는 것은 다른 현상입니다. 조상에 overflow: hidden이나 clip이 적용되어 잘리는 상황이라면 층의 숫자를 올리는 것만으로 해결되지 않습니다. 실습에는 겹침을 관찰할 수 있도록 그런 잘림을 추가하지 않았습니다. 개발자 도구에서 배경색을 잠시 바꿔 무엇이 위에서 덮고 있는지 보고, 조상의 overflow도 따로 살펴보세요.

또한 z-index만 바꿨는데 변화가 없는 경우 해당 요소에 그 속성이 적용되는 조건을 확인해야 합니다. 일반 흐름의 평범한 블록과 위치 지정 요소, Flex 항목은 조건이 같지 않습니다. 우선 요소의 배치 방식과 부모 관계를 적고 그다음 값의 비교 범위를 판단하면, 숫자를 바꿔 가며 우연한 결과를 찾는 시간을 줄일 수 있습니다.

같은 코드로 실패와 수정을 검증하세요

시작 파일은 수정 영역에서 부모 A를 올리는 선언만 비워 두었습니다. 그래서 화면 문구에는 수정 목표가 표시되어 있어도 실제 그림은 실패 영역과 같습니다. 연습용 규칙에 z-index: 3을 넣은 뒤 두 그림을 비교하세요. 수정 그림의 어두운 배지 오른쪽이 밝은 부모 B 위에 나타나야 합니다. 다시 규칙을 끄면 같은 부분이 가려져 원인과 결과를 확인할 수 있습니다.

작은 화면에서는 겹침 영역이 여전히 실험판 안에 있는지, 표의 값을 읽을 수 있는지 확인하세요. 이 예제는 포인터를 올려야만 차이가 드러나는 동작 없이 정적으로 비교하도록 만들었습니다. 층을 추가해야 할 때마다 본문, 헤더, 떠 있는 UI 등 역할별 규칙을 문서화하면 각 컴포넌트가 독자적으로 큰 숫자를 붙이는 상황을 줄일 수 있습니다. 값 자체보다 어떤 요소끼리 비교해야 하는지 설명할 수 있는 상태를 목표로 삼으세요.

자식의 z-index가 커도 부모의 쌓임 맥락을 벗어나지는 못합니다. 도식은 부모 A의 z-index를 1에서 3으로 바꾸면 부모 B보다 위로 이동하는 예시입니다.
자식의 z-index가 커도 부모의 쌓임 맥락을 벗어나지는 못합니다. 도식은 부모 A의 z-index를 1에서 3으로 바꾸면 부모 B보다 위로 이동하는 예시입니다.

완성 HTML 전체 코드

아래 코드를 index.html로 저장하고 같은 폴더에 style.css를 만드세요. 시작 파일의 연습용 주석을 먼저 작성한 뒤 완성본과 비교해도 좋습니다.

실습 파일

파일 경로를 확인하고 같은 프로젝트 안에 저장하세요. 이미지 등 소스 목록에 없는 파일과 실행 안내는 실습 ZIP에 포함되어 있습니다.

자식의 z-index 숫자만 높이지 않고 부모의 stacking context를 확인합니다. 수정 전은 시작 상태, 수정 후는 비교할 완성 상태입니다.

수정 전

index.html

<!doctype html>
<html lang="ko">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>CSS z-index가 안 먹힐 때: stacking context와 부모 층 디버깅</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <p class="note" style="padding: 16px">
      시작 파일: 수정 영역의 설명은 완성 목표입니다. style.css의 연습 주석에 안내된
      규칙을 적용해 보세요.
    </p>
    <main>
      <h1>자식의 숫자보다 부모의 층을 확인하세요</h1>
      <p>
        두 실험은 자식 배지의 z-index가 같습니다. 수정에서는 부모 A의 층만 바꿉니다.
      </p>
      <section>
        <h2>1. 실패: 부모 A 1, 부모 B 2</h2>
        <p>겹치는 부분에서 B가 배지보다 위에 보입니다.</p>
        <div
          class="stage broken-stage"
          role="img"
          aria-label="부모 A의 z-index는 1이고 자식 배지는 10입니다. 부모 B는 2이므로 배지의 오른쪽 겹침 부분을 가립니다."
        >
          <div class="panel-a">부모 A · 1<span class="label">자식 배지 · 10</span></div>
          <div class="panel-b">부모 B · 2</div>
        </div>
      </section>
      <section>
        <h2>2. 수정: 부모 A 3, 부모 B 2</h2>
        <p>A의 부모 층을 3으로 올리면 그 안의 배지도 B 위에 나타납니다.</p>
        <div
          class="stage fixed-stage"
          role="img"
          aria-label="수정 목표 영역입니다. 현재 부모 A는 1이므로 배지가 부모 B에 가려집니다."
        >
          <div class="panel-a">부모 A · 3<span class="label">자식 배지 · 10</span></div>
          <div class="panel-b">부모 B · 2</div>
        </div>
      </section>
      <section>
        <h2>3. 숫자를 비교하는 범위</h2>
        <table>
          <caption>
            현재 실습의 층 구조
          </caption>
          <thead>
            <tr>
              <th scope="col">요소</th>
              <th scope="col">실패</th>
              <th scope="col">수정</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <th scope="row">부모 A</th>
              <td>1</td>
              <td>3</td>
            </tr>
            <tr>
              <th scope="row">A 안의 배지</th>
              <td>10</td>
              <td>10</td>
            </tr>
            <tr>
              <th scope="row">부모 B</th>
              <td>2</td>
              <td>2</td>
            </tr>
          </tbody>
        </table>
        <p>
          배지 10과 B 2를 직접 비교하지 않습니다. 먼저 같은 단계에 있는 A와 B를
          비교합니다.
        </p>
      </section>
      <section>
        <h2>4. 개발자 도구 실험</h2>
        <ol>
          <li>수정 영역의 .panel-a에서 z-index: 3을 끄세요.</li>
          <li>배지가 다시 B 아래로 들어가는지 확인하세요.</li>
          <li>규칙을 되살린 뒤 부모 A 전체가 위로 이동한다는 점을 확인하세요.</li>
        </ol>
        <p class="note">
          이 그림은 층 비교 실험입니다. 실제 메뉴나 대화상자의 키보드 동작을 구현한
          예제는 아닙니다.
        </p>
      </section>
    </main>
  </body>
</html>

style.css

* {
  box-sizing: border-box;
}
body {
  margin: 0;
  background: #f4f4f4;
  color: #252525;
  font-family: system-ui, sans-serif;
  line-height: 1.65;
}
main {
  width: min(100% - 32px, 880px);
  margin: 40px auto;
}
h1 {
  font-size: clamp(1.6rem, 4vw, 2.5rem);
  line-height: 1.3;
}
h2 {
  font-size: 1.25rem;
}
section {
  margin-block: 32px;
  padding-block: 12px;
  border-top: 1px solid #aaa;
}
a {
  color: inherit;
  text-underline-offset: 4px;
}
a:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
  outline: 3px solid #444;
  outline-offset: 4px;
}
code {
  overflow-wrap: anywhere;
}
.note {
  color: #505050;
}
.stage {
  position: relative;
  isolation: isolate;
  height: 260px;
  background: #e5e5e5;
}
.panel-a {
  position: absolute;
  z-index: 1;
  top: 24px;
  left: 12px;
  width: 60%;
  height: 148px;
  padding: 16px;
  background: #bbb;
}
.label {
  position: absolute;
  z-index: 10;
  left: 12px;
  right: -56px;
  top: 70px;
  padding: 12px;
  background: #333;
  color: #fff;
}
.panel-b {
  position: absolute;
  z-index: 2;
  top: 80px;
  right: 12px;
  width: 45%;
  height: 152px;
  padding: 16px;
  background: #d0d0d0;
}
.fixed-stage .panel-a {
  /* 연습: 부모 A를 B보다 위로 올리세요. */
}
table {
  width: 100%;
  border-collapse: collapse;
  text-align: left;
}
caption {
  text-align: left;
  padding-block: 8px;
}
th,
td {
  padding: 10px;
  border-bottom: 1px solid #aaa;
}

수정 방법

index.html

index.html에서 아래 차이가 있는 위치를 수정하세요. -는 삭제할 줄, +는 추가할 줄이며 표시는 실제 코드에 넣지 않습니다.

자식의 z-index 숫자만 높이지 않고 부모의 stacking context를 확인합니다.

--- 수정 전/index.html
+++ 수정 후/index.html
@@ -7,10 +7,6 @@
     <link rel="stylesheet" href="style.css" />
   </head>
   <body>
-    <p class="note" style="padding: 16px">
-      시작 파일: 수정 영역의 설명은 완성 목표입니다. style.css의 연습 주석에 안내된
-      규칙을 적용해 보세요.
-    </p>
     <main>
       <h1>자식의 숫자보다 부모의 층을 확인하세요</h1>
       <p>
@@ -34,7 +30,7 @@
         <div
           class="stage fixed-stage"
           role="img"
-          aria-label="수정 목표 영역입니다. 현재 부모 A는 1이므로 배지가 부모 B에 가려집니다."
+          aria-label="부모 A의 z-index를 3으로 변경했습니다. 자식 배지 10은 그대로이며 배지가 부모 B보다 위에 보입니다."
         >
           <div class="panel-a">부모 A · 3<span class="label">자식 배지 · 10</span></div>
           <div class="panel-b">부모 B · 2</div>

수정 후 탭의 전체 파일과 비교한 뒤 본문의 확인 절차를 실행하세요.

style.css

style.css에서 아래 차이가 있는 위치를 수정하세요. -는 삭제할 줄, +는 추가할 줄이며 표시는 실제 코드에 넣지 않습니다.

자식의 z-index 숫자만 높이지 않고 부모의 stacking context를 확인합니다.

--- 수정 전/style.css
+++ 수정 후/style.css
@@ -77,7 +77,7 @@
   background: #d0d0d0;
 }
 .fixed-stage .panel-a {
-  /* 연습: 부모 A를 B보다 위로 올리세요. */
+  z-index: 3;
 }
 table {
   width: 100%;

수정 후 탭의 전체 파일과 비교한 뒤 본문의 확인 절차를 실행하세요.

수정 후

index.html

<!doctype html>
<html lang="ko">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>CSS z-index가 안 먹힐 때: stacking context와 부모 층 디버깅</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <main>
      <h1>자식의 숫자보다 부모의 층을 확인하세요</h1>
      <p>
        두 실험은 자식 배지의 z-index가 같습니다. 수정에서는 부모 A의 층만 바꿉니다.
      </p>
      <section>
        <h2>1. 실패: 부모 A 1, 부모 B 2</h2>
        <p>겹치는 부분에서 B가 배지보다 위에 보입니다.</p>
        <div
          class="stage broken-stage"
          role="img"
          aria-label="부모 A의 z-index는 1이고 자식 배지는 10입니다. 부모 B는 2이므로 배지의 오른쪽 겹침 부분을 가립니다."
        >
          <div class="panel-a">부모 A · 1<span class="label">자식 배지 · 10</span></div>
          <div class="panel-b">부모 B · 2</div>
        </div>
      </section>
      <section>
        <h2>2. 수정: 부모 A 3, 부모 B 2</h2>
        <p>A의 부모 층을 3으로 올리면 그 안의 배지도 B 위에 나타납니다.</p>
        <div
          class="stage fixed-stage"
          role="img"
          aria-label="부모 A의 z-index를 3으로 변경했습니다. 자식 배지 10은 그대로이며 배지가 부모 B보다 위에 보입니다."
        >
          <div class="panel-a">부모 A · 3<span class="label">자식 배지 · 10</span></div>
          <div class="panel-b">부모 B · 2</div>
        </div>
      </section>
      <section>
        <h2>3. 숫자를 비교하는 범위</h2>
        <table>
          <caption>
            현재 실습의 층 구조
          </caption>
          <thead>
            <tr>
              <th scope="col">요소</th>
              <th scope="col">실패</th>
              <th scope="col">수정</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <th scope="row">부모 A</th>
              <td>1</td>
              <td>3</td>
            </tr>
            <tr>
              <th scope="row">A 안의 배지</th>
              <td>10</td>
              <td>10</td>
            </tr>
            <tr>
              <th scope="row">부모 B</th>
              <td>2</td>
              <td>2</td>
            </tr>
          </tbody>
        </table>
        <p>
          배지 10과 B 2를 직접 비교하지 않습니다. 먼저 같은 단계에 있는 A와 B를
          비교합니다.
        </p>
      </section>
      <section>
        <h2>4. 개발자 도구 실험</h2>
        <ol>
          <li>수정 영역의 .panel-a에서 z-index: 3을 끄세요.</li>
          <li>배지가 다시 B 아래로 들어가는지 확인하세요.</li>
          <li>규칙을 되살린 뒤 부모 A 전체가 위로 이동한다는 점을 확인하세요.</li>
        </ol>
        <p class="note">
          이 그림은 층 비교 실험입니다. 실제 메뉴나 대화상자의 키보드 동작을 구현한
          예제는 아닙니다.
        </p>
      </section>
    </main>
  </body>
</html>

style.css

* {
  box-sizing: border-box;
}
body {
  margin: 0;
  background: #f4f4f4;
  color: #252525;
  font-family: system-ui, sans-serif;
  line-height: 1.65;
}
main {
  width: min(100% - 32px, 880px);
  margin: 40px auto;
}
h1 {
  font-size: clamp(1.6rem, 4vw, 2.5rem);
  line-height: 1.3;
}
h2 {
  font-size: 1.25rem;
}
section {
  margin-block: 32px;
  padding-block: 12px;
  border-top: 1px solid #aaa;
}
a {
  color: inherit;
  text-underline-offset: 4px;
}
a:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
  outline: 3px solid #444;
  outline-offset: 4px;
}
code {
  overflow-wrap: anywhere;
}
.note {
  color: #505050;
}
.stage {
  position: relative;
  isolation: isolate;
  height: 260px;
  background: #e5e5e5;
}
.panel-a {
  position: absolute;
  z-index: 1;
  top: 24px;
  left: 12px;
  width: 60%;
  height: 148px;
  padding: 16px;
  background: #bbb;
}
.label {
  position: absolute;
  z-index: 10;
  left: 12px;
  right: -56px;
  top: 70px;
  padding: 12px;
  background: #333;
  color: #fff;
}
.panel-b {
  position: absolute;
  z-index: 2;
  top: 80px;
  right: 12px;
  width: 45%;
  height: 152px;
  padding: 16px;
  background: #d0d0d0;
}
.fixed-stage .panel-a {
  z-index: 3;
}
table {
  width: 100%;
  border-collapse: collapse;
  text-align: left;
}
caption {
  text-align: left;
  padding-block: 8px;
}
th,
td {
  padding: 10px;
  border-bottom: 1px solid #aaa;
}

index.html 전체 코드 보기

완성 CSS 전체 코드

style.css 전체 코드 보기

실습 파일과 이어서 읽기

시작 파일·완성 파일·한국어 README 내려받기

일반적인 화면 배치는 Flex와 미디어 쿼리 반응형 기초, 행과 열의 설계는 CSS Grid 상세 가이드에서 이어서 살펴보세요.

공식 참고 문서

이 글이 도움이 되었나요?

조회 중

웹 퍼블리싱 학습 순서

필수 13개 · 전체 15개

읽음 기록 관리

전체 과정 목차 (15개)
  1. 필수 학습 · HTML 입문: 시맨틱 문서와 접근 가능한 폼 만들기
  2. 필수 학습 · CSS 입문: 박스 모델·선택자·캐스케이드 직접 확인하기
  3. 필수 학습 · CSS Flex와 미디어 쿼리 입문: 반응형 카드 페이지 만들기
  4. 필수 학습 · CSS position 완전 이해: relative·absolute·fixed·sticky 실습
  5. 필수 학습 · HTML details와 summary로 접근 가능한 FAQ 만들기
  6. 필수 학습 · CSS Grid 실습: Flex 학습 카드로 열 맞추기와 auto-fit 비교
  7. 필수 학습 · CSS overflow 해결: 긴 텍스트·min-width: 0·말줄임 실습
  8. 필수 학습 · HTML 표 만들기: caption·scope로 비교표를 읽기 쉽게, 모바일에서는 표만 스크롤하기
  9. 필수 선수 · HTML CSS 이미지 문제 해결 실습: 경로·비율·잘림·흐림·CLS
  10. 필수 학습 · Container Query 실습: 같은 카드를 본문과 사이드바에 배치하기
  11. 선택 참고 · CSS Baseline 보는 법: 지원 범위와 폴백을 직접 확인하기
  12. 필수 학습 · CSS z-index가 안 먹힐 때: stacking context와 부모 층 디버깅 현재 글
  13. 필수 선수 · 웹 접근성 기초 점검: 키보드 순서·초점 표시·확대 화면을 직접 확인하는 체크리스트
  14. 필수 학습 · HTML CSS 종합 실습: 소개·카드·이미지·요금표·문의 폼·FAQ가 있는 반응형 서비스 페이지
  15. 선택 참고 · 프론트엔드 SEO 기초: HTML 구조와 이미지 성능 진단하기

새 글 받아보기

RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.

RSS 피드 구독하기

댓글 남기기