z-index 값을 높여도 요소가 가려지면 무엇을 확인해야 하나요?
자식의 z-index는 부모의 쌓임 영역 안에서 비교됩니다. 숫자를 키워도 가려지면 다른 부모와의 순서부터 확인합니다. 낮은 부모 안의 자식은 큰 숫자만으로 그 영역을 벗어날 수 없습니다.
본문 근거: developer.mozilla.org/en-US/docs/Web/CSS/Guides/Positioned_layout/Stacking_context
독립 재현: 자식 9999가 가려지는 이유
CSS z-index가 안 먹힐 때: stacking context와 부모 층 디버깅 · 독립 실습
자식 z-index가 9999여도 부모 쌓임 맥락을 벗어나지 못하는 결과를 확인하세요.
예제 크게 보기 (새 탭)독자 질문: 자식의 z-index가 9999인데 왜 옆 요소 뒤에 있나요? 아래 전체 코드를 새 stacking-test.html에 저장해 브라우저로 엽니다. TaskFlow 파일은 필요하지 않습니다.
<!doctype html>
<html lang="ko">
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>stacking context 실습</title>
<style>
.lower { position: relative; z-index: 1; height: 100px; background: #dbeafe; }
.tip { position: absolute; top: 65px; left: 20px; z-index: 9999; padding: 12px; background: #fef08a; }
.upper { position: relative; z-index: 2; margin-top: -10px; height: 80px; background: #fecaca; }
</style>
<div class="lower">부모 A (z-index: 1)
<div class="tip">자식 (z-index: 9999)</div>
</div>
<div class="upper">부모 B (z-index: 2)</div>
</html>
예상: 노란 자식이 빨간 부모 B와 겹치는 부분에서 가려집니다. DevTools로 lower와 upper의 position·z-index를 확인하세요. 두 부모는 각각 stacking context를 만들고, 루트에서는 부모 A의 1과 부모 B의 2가 비교됩니다. 자식의 9999는 부모 A 내부에서만 비교됩니다.
tip의 9999를 더 큰 수로 바꿔도 빨간 영역 위로 올라오지 않는지 확인합니다.lower의z-index를 3으로 바꾸면 노란 자식뿐 아니라 부모 A 전체가 부모 B 위로 올라옵니다. 이 순서가 화면 의도와 맞지 않으면 숫자를 더 키우지 말고 팝업을 낮은 부모 context 밖에 놓을 구조인지 판단합니다.
완료 기준: 겹침의 원인을 부모 A와 B의 비교로 설명하고, 자식 숫자 변경과 부모 순서 변경의 결과 차이를 직접 확인합니다. 실제 브라우저 조작 결과는 독자가 확인할 항목이며 본문에서 대신 실행했다고 주장하지 않습니다.
단순 z-index
같은 stacking context 안에 있다면 큰 z-index가 앞에 표시됩니다.
.card-a {
position: relative;
z-index: 1;
}
.card-b {
position: relative;
z-index: 2;
}
card-b가 card-a보다 위에 나타납니다.
별도 stacking context
부모 A:
.parent-a {
position: relative;
z-index: 1;
}
그 안의 child:
.child {
position: absolute;
z-index: 9999;
}
부모 B:
.parent-b {
position: relative;
z-index: 2;
}
child의 9999는 parent-a context 안에서만 의미가 있습니다.
parent-a 자체가 parent-b보다 아래라면 child가 parent-b 위로 올라오지 못할 수 있습니다.

stacking context를 만드는 대표 조건
대표적으로 다음 상황에서 새 context가 만들어질 수 있습니다.
- root html
- positioned element + non-auto z-index
- fixed
- sticky
- opacity가 1보다 작음
- transform
- filter
- isolation:isolate
- 일부 contain 값
- container-type 등 containment 관련 조건
실제 최신 목록은 MDN을 확인합니다.
transform이 의외의 원인
.panel {
transform:
translateZ(0);
}
성능 최적화를 위해 무심코 넣은 transform이 stacking context를 만들 수 있습니다.
z-index 문제를 볼 때 부모 chain의 transform/opacity도 확인합니다.
fixed도 항상 최상단은 아닙니다
fixed 요소도 stacking context와 조상 관계의 영향을 받을 수 있습니다.
무조건 z-index: 999999를 주기보다 DOM 위치와 context를 점검합니다.
top layer
dialog의 modal 상태, popover 등 일부 웹 플랫폼 요소는 일반 z-index stacking보다 위의 top layer에서 관리될 수 있습니다.
직접 overlay를 만들 때 일반 stacking context와 native top-layer API 차이를 알아두면 좋습니다.
디버깅 순서
1. 겹치는 두 요소 확인
2. 둘이 같은 stacking context인지 확인
3. 각 부모를 위로 따라가며 context 생성 조건 확인
4. z-index가 부모 context 안에서만 비교되는지 확인
5. 불필요한 transform/opacity/z-index 제거
6. 필요한 최소 layer token으로 정리
z-index scale
프로젝트에서 다음처럼 역할별 작은 범위를 사용할 수 있습니다.
:root {
--z-header: 10;
--z-floating: 20;
--z-overlay: 30;
}
숫자 크기보다 레이어 관계가 중요합니다.
자주 발생하는 오류와 해결
z-index를 계속 큰 숫자로 올립니다
자식이 다른 stacking context 안에 있다면 숫자를 키워도 형제 context 밖으로 나오지 못할 수 있습니다. 부모 context부터 확인합니다.
문제 요소만 보고 부모 CSS를 확인하지 않습니다
transform, opacity, contain, positioned element의 z-index 등 조상이 새 stacking context를 만드는지 위로 따라가며 확인합니다.
fixed 요소면 항상 최상단이라고 생각합니다
fixed도 stacking context 관계를 고려해야 합니다. DOM 위치와 조상 context를 함께 확인합니다.
학습 완료 체크리스트
- z-index가 같은 stacking context 안에서 비교된다는 것을 이해했습니다.
- 부모 stacking context가 자식 레이어를 제한할 수 있다는 것을 설명할 수 있습니다.
- transform/opacity 등 context 생성 원인을 점검할 수 있습니다.
- 무작정 큰 z-index를 사용하지 않을 수 있습니다.
- top layer와 일반 stacking context가 다른 개념임을 알고 있습니다.
독립 실습은 여기서 마칠 수 있습니다. 아래 선택 실습은 TaskFlow 프로젝트 파일로 진행합니다.
선택 실습: 할 일 앱에 적용하기
TaskFlow는 간단한 서비스 화면에서 시작해 할 일 앱으로 이어 만드는 예제입니다. 이 선택 실습에서는 HTML과 CSS로 이번 화면을 만듭니다.
sticky header, card badge, fixed help button의 레이어를 작은 scale로 정리하고 일부러 context 문제를 재현합니다.
선택: 연재 프로젝트와 전체 자료
연재 프로젝트를 이어 만드는 분을 위한 자료입니다. 이 글의 개념과 예제만 찾으셨다면 필요한 파일과 실행 방법만 펼쳐보세요.
TaskFlow 프로젝트 적용
CSS z-index가 안 먹힐 때: stacking context와 부모 층 디버깅 · 단계별 TaskFlow
TaskFlow 누적 12단계입니다. 자식 z-index가 9999여도 부모 쌓임 맥락을 벗어나지 못하는 결과를 확인하세요.
예제 크게 보기 (새 탭)실행 가능한 시작본과 이번 단계
이번 단계 시작 ZIP을 새 폴더에 압축 해제합니다. 이 ZIP은 직전 단계의 완료 파일과 같으며 앞 글의 코드를 따로 모을 필요가 없습니다. 이번 단계 완료 ZIP은 다른 폴더에 풀어 비교합니다.
header·도움 링크의 z-index를 작은 변수 체계로 정리합니다. fixed 도움 링크는 transform 조상 없이 body 바로 아래에 유지합니다.
style.css: 이 파일의 기존 내용을 아래 완료 파일 전체와 대조하여 교체합니다. 다른 파일은 유지합니다.
짧은 코드 예제는 변경 위치를 설명합니다. 파일 전체를 작성하거나 교체할 때는 완료 파일 전체를 사용하세요. 생략 표시가 있는 부분 코드를 파일 전체로 저장하지 않습니다.
실행: ZIP의 index.html을 브라우저에서 엽니다. 외부 서버나 npm 설치 없이 로컬 HTML·CSS·이미지를 사용합니다. 문의 폼은 서버 전송을 연결하지 않은 문서 예제입니다. 연습 값만 사용하고 입력 형식을 확인합니다.
직접 확인할 결과: --z-header 10, --z-floating 20이며 도움 링크의 상위 요소가 body입니다.
전체 파일·코드 보기
완료 파일 전체
index.html 전체 파일
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>
TaskFlow
</title>
<link href="./style.css" rel="stylesheet"/>
</head>
<body>
<header class="site-header">
<div class="page site-header__inner">
<a class="brand" href="#home">
TaskFlow
</a>
<nav aria-label="주요 메뉴" class="nav">
<a href="#features">
기능
</a>
<a href="#contact">
문의
</a>
</nav>
</div>
</header>
<main class="page" id="home">
<div class="service-layout">
<div>
<section class="hero">
<h1>
해야 할 일을 한눈에 정리하세요
</h1>
<p>
TaskFlow는 오늘의 작업을 빠르게 정리하는 서비스입니다.
</p>
<img alt="TaskFlow 서비스 소개 페이지의 기능·문의 화면" class="hero__image" height="800" src="./images/hero.webp" width="1200"/>
</section>
<section id="features">
<h2>
주요 기능
</h2>
<div class="feature-grid">
<article class="card feature-card">
<span class="feature-card__badge">
NEW
</span>
<h3>
빠른 작업 추가
</h3>
<p>
해야 할 일을 바로 기록할 수 있습니다.
</p>
<img alt="TaskFlow 기능 카드 세 개" class="feature-card__image" height="450" src="./images/feature-board.webp" width="800"/>
</article>
<article class="card">
<h3>
간단한 분류
</h3>
<p>
해야 할 일을 구분합니다.
</p>
</article>
<article class="card">
<h3>
진행 확인
</h3>
<p>
완료 여부를 확인합니다.
</p>
</article>
<article class="card task-preview">
<div class="task-preview__content">
<h3>
아주 긴 작업 제목과 URL
</h3>
<p>
https://example.com/very-long-task-url-without-normal-break-points-123456789
</p>
</div>
<a href="#contact">
문의
</a>
</article>
</div>
</section>
<section class="faq" id="faq">
<h2>
자주 묻는 질문
</h2>
<details>
<summary>
TaskFlow는 어떤 서비스인가요?
</summary>
<p>
해야 할 일을 빠르게 정리하는 작업 관리 서비스 예제입니다.
</p>
</details>
<details>
<summary>
모바일에서도 볼 수 있나요?
</summary>
<p>
작은 화면부터 설계한 반응형 레이아웃을 사용합니다.
</p>
</details>
<details>
<summary>
문의는 어디서 하나요?
</summary>
<p>
아래 문의 폼을 이용할 수 있습니다.
</p>
</details>
</section>
<section id="pricing">
<h2>
요금제
</h2>
<div class="pricing-grid">
<article class="card">
<h3>
Basic
</h3>
<p>
개인 학습용
</p>
</article>
<article class="card">
<h3>
Pro
</h3>
<p>
더 많은 프로젝트
</p>
</article>
</div>
<div aria-label="요금제 비교표 가로 스크롤" class="table-scroll" role="region" tabindex="0">
<table class="pricing-table">
<caption>
TaskFlow 요금제 기능 비교
</caption>
<thead>
<tr>
<th scope="col">
항목
</th>
<th scope="col">
Basic
</th>
<th scope="col">
Pro
</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">
프로젝트
</th>
<td>
3개
</td>
<td>
무제한
</td>
</tr>
<tr>
<th scope="row">
작업 보관
</th>
<td>
30일
</td>
<td>
무제한
</td>
</tr>
<tr>
<th scope="row">
팀 공유
</th>
<td>
없음
</td>
<td>
지원
</td>
</tr>
</tbody>
</table>
</div>
</section>
<section id="contact">
<h2>
문의하기
</h2>
<form>
<div class="form-field">
<label for="name">
이름
</label>
<input autocomplete="name" id="name" name="name" type="text"/>
</div>
<div class="form-field">
<label for="email">
이메일
</label>
<input autocomplete="email" id="email" name="email" type="email"/>
</div>
<div class="form-field">
<label for="message">
문의 내용
</label>
<textarea id="message" name="message" rows="5"></textarea>
</div>
<button type="submit">
문의 보내기
</button>
</form>
</section>
<div class="feature-slot">
<article class="card feature-card">
<img alt="TaskFlow 기능 카드 세 개" class="feature-card__image" height="450" src="./images/feature-board.webp" width="800"/>
<div>
<h3>
작업 카드
</h3>
<p>
넓은 공간에서는 이미지와 설명을 나란히 배치합니다.
</p>
</div>
</article>
</div>
</div>
<aside aria-label="기능 미리보기">
<div class="feature-slot">
<article class="card feature-card">
<img alt="TaskFlow 기능 카드 세 개" class="feature-card__image" height="450" src="./images/feature-board.webp" width="800"/>
<div>
<h3>
작업 카드
</h3>
<p>
넓은 공간에서는 이미지와 설명을 나란히 배치합니다.
</p>
</div>
</article>
</div>
</aside>
</div>
</main>
<footer class="site-footer">
<div class="page">
<p>
© TaskFlow
</p>
</div>
</footer>
<a class="help-button" href="#contact">
문의
</a>
</body>
</html>
style.css 전체 파일
:root {
color: #171717;
background: #f5f5f5;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
font-family:
Arial,
sans-serif;
line-height: 1.6;
color: #171717;
background: #f5f5f5;
}
.page {
width: min(
100% - 32px,
960px
);
margin-inline: auto;
}
.site-header,
.site-footer {
padding-block: 20px;
}
main {
padding-block: 48px;
}
section + section {
margin-top: 64px;
}
.card {
padding: 24px;
border: 1px solid #d7d7d7;
background: #fff;
}
form {
display: grid;
gap: 16px;
}
.form-field {
display: grid;
gap: 8px;
}
input,
textarea {
width: 100%;
min-height: 44px;
padding: 10px 12px;
border: 1px solid #b8b8b8;
background: #fff;
}
button {
min-height: 44px;
padding: 10px 16px;
border: 1px solid #171717;
background: #171717;
color: #fff;
}
a { color: inherit; }
input, textarea, button { font: inherit; }
h1 { font-size: 32px; font-weight: 700; }
h2 { font-size: 24px; }
h3 { font-size: 20px; }
.site-header__inner {
display: flex;
flex-direction: column;
gap: 12px;
}
.nav {
display: flex;
flex-wrap: wrap;
gap: 8px 16px;
}
.feature-list {
display: flex;
flex-direction: column;
gap: 16px;
}
@media (min-width: 48rem) {
.site-header__inner {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.feature-list {
flex-direction: row;
}
.feature-list > * {
flex: 1;
}
}
.site-header {
position: sticky;
top: 0;
z-index: 10;
background: #f5f5f5;
border: 1px solid #ddd;
}
.feature-card {
position: relative;
}
.feature-card__badge {
position: absolute;
top: 12px;
right: 12px;
padding: 4px 8px;
border: 1px solid #171717;
background: #fff;
font-size: 12px;
}
.help-button {
position: fixed;
right: 16px;
bottom: 16px;
min-width: 44px;
min-height: 44px;
padding: 10px 14px;
border: 1px solid #171717;
background: #171717;
color: #fff;
text-decoration: none;
}
.faq {
display: grid;
gap: 12px;
}
.faq details {
border: 1px solid #d7d7d7;
background: #fff;
}
.faq summary {
padding: 16px;
cursor: pointer;
font-weight: 700;
}
.faq details p {
margin: 0;
padding: 0 16px 16px;
}
.faq summary:focus-visible {
outline: 2px solid #171717;
outline-offset: 2px;
}
.feature-grid,
.pricing-grid {
display: grid;
grid-template-columns:
repeat(
auto-fit,
minmax(
min(100%, 220px),
1fr
)
);
gap: 16px;
}
.task-preview {
display: flex;
align-items: flex-start;
gap: 12px;
}
.task-preview__content {
min-width: 0;
flex: 1;
}
.task-preview__content p {
overflow-wrap: anywhere;
}
.settings-layout {
display: grid;
grid-template-columns:
180px
minmax(0, 1fr);
gap: 24px;
}
.table-scroll {
overflow-x: auto;
}
.pricing-table {
width: 100%;
min-width: 640px;
border-collapse: collapse;
background: #fff;
}
.pricing-table caption {
margin-bottom: 12px;
text-align: left;
font-weight: 700;
}
.pricing-table th,
.pricing-table td {
padding: 12px;
border: 1px solid #d7d7d7;
text-align: left;
}
img {
display: block;
max-width: 100%;
}
.hero__image {
width: 100%;
height: auto;
}
.feature-card__image {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
}
.service-layout {
display: grid;
gap: 24px;
}
@media (
min-width: 64rem
) {
.service-layout {
grid-template-columns:
minmax(0, 1fr)
280px;
}
}
.feature-slot {
container-type:
inline-size;
}
.feature-card {
display: grid;
gap: 16px;
}
@container (
min-width: 30rem
) {
.feature-card {
grid-template-columns:
180px
minmax(0, 1fr);
align-items: center;
}
}
.service-layout > * { min-width: 0; }
:root {
--z-header: 10;
--z-floating: 20;
}
.site-header {
position: sticky;
top: 0;
z-index:
var(--z-header);
}
.help-button {
position: fixed;
right: 16px;
bottom: 16px;
z-index:
var(--z-floating);
}
위 파일과 ZIP의 동명 파일은 동일합니다. 회차별 README·CHANGES 문서는 ZIP에 포함합니다. 생성 산출물·node_modules·실제 환경 변수 파일은 포함하지 않습니다.
style.css
:root {
--z-header: 10;
--z-floating: 20;
}
.site-header {
position: sticky;
top: 0;
z-index:
var(--z-header);
}
.help-button {
position: fixed;
right: 16px;
bottom: 16px;
z-index:
var(--z-floating);
}
시작본과 이번 변경
CSS Baseline 보는 법: 지원 범위와 폴백을 직접 확인하기의 완료 체크포인트를 시작본으로 사용합니다.
style.css의 레이어 변수를 정리하고 index.html 도움 링크의 조상 transform 여부를 확인합니다. 자식 숫자만 올린 경우와 부모 context를 바꾼 경우를 비교해 의도한 순서를 기록합니다.
연속 프로젝트 선수 조건
- position과 z-index 기본을 알고 있어야 합니다.
- DevTools에서 요소의 부모 구조와 적용된 CSS를 추적할 수 있어야 합니다.
이전 체크포인트
publishing-step-11-baseline
이번 목표
sticky header, card badge, fixed help button의 레이어를 작은 scale로 정리하고 일부러 context 문제를 재현합니다.
재현
help-button을 transform이 있는 낮은 z-index 부모 안에 넣어 문제를 확인합니다.
최종 구조에서는 viewport fixed control을 불필요한 parent context 밖으로 옮기고 context hierarchy를 단순하게 유지합니다.
실행 확인
- header와 fixed button이 의도한 순서로 보이는가
- card badge가 card 내부에서만 우선순위를 갖는가
- 부모 transform을 추가/제거했을 때 stacking 차이를 관찰했는가
- z-index 숫자를 무한히 키우지 않았는가
완료 체크포인트
publishing-step-12-stacking-context
다음 학습
다음 글에서는 지금까지 만든 TaskFlow 전체를 키보드·focus·200% zoom·source order 관점에서 종합 점검합니다.
공식 문서
- MDN Stacking context: https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Positioned_layout/Stacking_context
글 소개·학습 준비·전체 목차

웹 퍼블리싱 · 12 / 15 · 고급 오류 해결 · z-index와 stacking context
z-index를 10에서 999999로 올렸는데도 요소가 뒤에 보인다면 숫자가 작은 것이 원인이 아닐 수 있습니다.
CSS는 요소를 하나의 전역 z-index 숫자로만 정렬하지 않습니다. 일부 요소는 독립된 stacking context를 만들고, 그 안의 자식은 부모 context 밖의 요소와 직접 경쟁하지 못합니다.
이번 글에서는 stacking context의 계층 구조를 기준으로 문제를 해결합니다.
이번 글에서 배우는 내용
- z-index
- stacking context
- 부모 context와 자식 z-index
- position + z-index
- opacity
- transform
- contain
- fixed/sticky
- DevTools로 레이어 문제를 추적하는 순서
먼저 알아야 할 내용
position과 z-index, 부모/자식 DOM 구조를 알면 됩니다. 낮은 부모에 있는 높은 z-index 자식으로 재현합니다.
목차
이 글이 도움이 되었나요?
읽음 기록
읽음 — / 전체 15개 · 필수 13개
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.