AD JB Toolbox 01

CSS 강좌 / z-index와 쌓임 맥락 이해하기

z-index는 요소의 겹침 순서를 조절하지만, 값이 크다고 페이지의 모든 요소 위로 올라가는 것은 아닙니다. 요소는 자신의 쌓임 맥락(stacking context) 안에서 순서를 비교하며, 다른 부모 맥락과의 앞뒤 관계는 자식 숫자보다 부모 맥락의 순서에 좌우됩니다.

겹침 순서와 위치를 구분하기

겹침 순서를 비교하려면 두 요소가 실제로 같은 화면 영역을 차지해야 합니다. 왼쪽과 오른쪽에 완전히 떨어져 있거나 위아래로 떨어져 있으면 z-index를 바꿔도 차이가 보이지 않습니다. 먼저 개발자 도구에서 요소의 사각형 위치를 확인하고, 겹치는 구역이 존재하는지부터 판단하세요.

position은 좌표 기준과 흐름 참여를 정하고 z-index는 겹칠 때의 앞뒤를 정합니다. top으로 좌표를 바꾸는 것과 z-index로 순서를 바꾸는 것은 다른 작업입니다. 아래 예제는 배지와 카드의 위치를 고정한 뒤 부모의 z-index만 바꾸므로 두 기능을 섞지 않고 비교할 수 있습니다.

z-index는 relative·absolute 같은 positioned 요소에 적용할 수 있으며 Flex 아이템과 Grid 아이템은 position: static이어도 사용할 수 있습니다. 보통의 일반 흐름 블록에 숫자만 지정하고 효과를 기대하면 적용 조건이 맞지 않을 수 있습니다. MDN의 z-index 설명에서 대상과 값의 의미를 확인할 수 있습니다.

쌓임 맥락은 부모 그룹 단위로 비교됨

쌓임 맥락은 내부 요소들의 앞뒤 순서를 하나의 그룹 안에서 계산하는 범위입니다. 부모 맥락은 바깥의 비교에서 하나의 단위처럼 취급됩니다. 왼쪽 부모가 1, 오른쪽 카드가 2인 같은 바깥 맥락에서 왼쪽 부모의 자식이 9999여도 그 자식은 부모 그룹 밖에서 오른쪽 카드의 2와 직접 경쟁하지 않습니다.

왼쪽 부모 맥락: z-index 1
  └ 자식 배지: z-index 9999
오른쪽 카드 맥락: z-index 2

바깥에서는 부모 1과 카드 2를 비교합니다.

반대로 왼쪽 부모를 3으로 높이면 부모 그룹 전체가 오른쪽 카드 2보다 앞에 놓입니다. 자식 배지의 숫자는 그대로여도 겹치는 부분에서 배지가 보입니다. 내부 숫자와 외부 숫자를 더해 10000과 2를 비교하는 방식도 아닙니다. 실제 그룹 생성 조건과 중첩 관계는 MDN의 쌓임 맥락 안내에서 확인할 수 있습니다.

어떤 선언이 새 쌓임 맥락을 만드는가

대표 조건 새 맥락 생성 확인할 점
relative 또는 absolute + z-index가 auto가 아님 생성 0도 숫자이며 auto와 다름
fixed 또는 sticky 생성 z-index가 auto여도 맥락을 만듦
Flex 또는 Grid 아이템 + non-auto z-index 생성 position 없이도 가능
opacity가 1보다 작음 생성 보이는 정도뿐 아니라 그룹 관계에 영향
transform이 none이 아님 생성 이동량이 0이어도 none과 다름
isolation: isolate 생성 의도적으로 별도 맥락을 만드는 방법

위 표는 자주 만나는 조건이며 모든 생성 조건의 목록은 아닙니다. 단순히 relative만 지정하고 z-index가 auto인 경우와 relative에 z-index: 0을 지정한 경우를 구분해야 합니다. 겹침이 달라졌다면 숫자뿐 아니라 조상에 새로 추가된 opacity나 transform도 확인하세요.

같은 맥락 안에서는 적용 조건이 맞는 요소의 z-index 숫자로 앞뒤를 비교할 수 있습니다. 다만 전체 CSS 그리기 순서는 음수 레벨, 일반 흐름, positioned 요소 등 여러 단계가 있으므로 모든 요소의 auto를 숫자 0과 동일하다고 취급하거나 숫자만으로 전체 페이지 순서를 계산하지 마세요. 학습 실습은 양수 숫자와 명시적인 부모 맥락으로 조건을 제한합니다.

전체 예제로 자식과 부모의 숫자 비교하기

아래 코드를 stacking-context.html로 저장해 엽니다. 배지는 위쪽 80~144px, 오른쪽 카드는 110~230px 영역에 놓여 세로로 34px 겹칩니다. 좌우 위치는 부모 너비에 대한 비율을 사용해 모바일에서도 겹침을 유지합니다. 부모와 아이템은 border-box로 계산하며 컨테이너 밖으로 불필요하게 넘치지 않도록 구성했습니다.

<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>stacking-context.html</title>
  <style>
* { box-sizing: border-box; }
body { margin: 16px; font: 16px/1.6 sans-serif; color: #243447; }
h1 { font-size: 24px; } h2 { font-size: 20px; margin-top: 32px; }
a { color: #145f8f; } a:focus-visible { outline: 3px solid #b4470b; outline-offset: 3px; }
.scene { position: relative; width: min(100%, 520px); height: 300px; background: #f1f4f7; }
 .left { position: absolute; z-index: 1; left: 0; top: 20px; width: 60%; height: 180px;
 padding: 16px; background: #ffe3b5; }
 .badge { position: absolute; z-index: 9999; left: 70%; top: 60px; width: 50%; height: 64px;
 padding: 8px; background: #f5a7cc; }
 .right { position: absolute; z-index: 2; left: 50%; top: 110px; width: 45%; height: 120px;
 padding: 12px; background: #9cd4f2; }
 .raised .left { z-index: 3; }
  </style>
</head>
<body>
<h1>자식 숫자와 부모 숫자</h1><h2>부모 1, 오른쪽 카드 2</h2><div class="scene original"><div class="left">왼쪽 부모<div class="badge">자식 배지<br>9999</div></div><div class="right">오른쪽 카드<br>z-index: 2</div></div><h2>부모 3, 오른쪽 카드 2</h2><div class="scene raised"><div class="left">왼쪽 부모<div class="badge">자식 배지<br>9999</div></div><div class="right">오른쪽 카드<br>z-index: 2</div></div>
</body>
</html>

첫 구역에서는 겹치는 부분의 분홍 배지가 파란 카드에 가려지고, 둘째 구역에서는 분홍 배지가 파란 카드 위에 보입니다. 두 구역의 차이는 왼쪽 부모의 1과 3뿐입니다. 아래 화면은 동일한 예제의 두 상태를 비교한 것입니다.

왼쪽 부모의 z-index가 1이면 배지가 파란 카드 뒤에 있고 3이면 배지가 카드 위로 올라오는 실제 예제 비교

  1. 첫 구역의 .badge 숫자만 99999로 높입니다. 부모 1과 카드 2의 관계가 유지되어 겹치는 부분은 계속 카드에 가립니다. 숫자를 9999로 복원합니다.
  2. 첫 구역에만 .original .left { z-index: 3; }을 추가합니다. 좌표를 바꾸지 않아도 배지가 카드 위로 올라옵니다. 확인 후 추가한 규칙을 제거합니다.
  3. 원래 첫 구역에서 .left의 z-index만 auto로 바꿉니다. 다른 생성 속성이 없는 이 예제에서는 왼쪽 부모가 별도 맥락을 만들지 않아 배지의 맥락이 바깥 카드와 비교됩니다. 배지가 앞에 보이는지 확인한 뒤 1로 복원합니다.
  4. 화면을 좁히거나 넓혀 배지와 카드가 계속 겹치는지 확인합니다. 좌표가 떨어진 상태에서는 숫자 변화가 눈에 보이지 않으므로 겹침 구역을 유지하면서 비교해야 합니다.

overflow로 잘린 내용은 별개의 문제

조상이 overflow: hidden으로 콘텐츠를 자르면 그 경계 밖의 부분은 높은 z-index만으로 다시 보이게 할 수 없습니다. 순서는 앞인데도 보이지 않는 경우에는 다른 요소에 덮였는지, 조상 경계에서 잘렸는지를 구분해야 합니다. 아래 코드는 stacking-clip.html로 저장해 실행합니다.

<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>stacking-clip.html</title>
  <style>
* { box-sizing: border-box; }
body { margin: 16px; font: 16px/1.6 sans-serif; color: #243447; }
h1 { font-size: 24px; } h2 { font-size: 20px; margin-top: 32px; }
a { color: #145f8f; } a:focus-visible { outline: 3px solid #b4470b; outline-offset: 3px; }
.frame { position: relative; width: min(100%, 260px); height: 100px; overflow: hidden;
 border: 2px solid #789; background: #e8f5de; }
 .popup { position: absolute; z-index: 9999; left: 80%; top: 24px; width: 90px; height: 60px;
 padding: 8px; background: #f5a7cc; }
  </style>
</head>
<body>
<h1>숫자를 높여도 잘리는 상자</h1><div class="frame">부모 경계<div class="popup">배지<br>9999</div></div><p>배지의 오른쪽 부분은 부모의 경계 밖에 있습니다.</p>
</body>
</html>
  1. 배지의 오른쪽이 부모 경계에서 잘리는지 확인합니다. z-index만 99999로 바꿔도 잘린 부분은 나타나지 않습니다. 숫자를 9999로 복원합니다.
  2. .frame의 overflow만 visible로 바꿉니다. 다른 좌표와 숫자는 그대로인데 경계 밖의 부분이 보입니다. 비교 후 hidden으로 되돌립니다.

실제 화면에서는 overflow를 제거하면 스크롤과 둥근 모서리 등 다른 목적에 영향을 줄 수 있습니다. 먼저 잘림이 의도된 것인지 판단하고, 팝업이 경계 밖에 있어야 한다면 잘림 조상 밖으로 구조를 옮기거나 적절한 별도 표시 방식을 검토하세요. 메뉴가 가려진다는 이유만으로 모든 부모의 overflow를 일괄 변경하지 마세요.

가려진 요소를 확인하는 순서

  1. 요소가 실제로 존재하고 display·visibility·opacity 때문에 숨겨진 것은 아닌지 확인합니다.
  2. 두 요소의 사각형 위치를 확인하여 실제 겹침 구역이 있는지 확인합니다.
  3. 대상 요소와 조상을 따라 올라가 쌓임 맥락을 만드는 선언을 찾습니다. 경쟁하는 맥락이 어느 공통 부모에서 비교되는지 확인합니다.
  4. 바꿀 숫자는 자식인지 부모 맥락인지 판단합니다. 영향을 받는 형제 요소도 함께 확인하고 필요한 범위에서만 값을 조절합니다.
  5. 순서를 바꿔도 안 보이면 overflow·clip 같은 잘림과 다른 좌표 기준을 점검합니다. 비교를 끝내면 임시 수정 값을 원래대로 복원합니다.

일반 요소의 z-index는 top layer에 올라간 dialog 같은 별도 표시 계층을 숫자만 높여 넘는 수단으로 생각해서는 안 됩니다. 기본 카드와 배지의 문제를 해결할 때는 먼저 실제 부모 맥락과 잘림을 이해하는 것이 우선입니다. 숫자를 계속 키우는 대신 어떤 그룹끼리 비교되는지 확인하면 작은 값으로도 의도한 순서를 구성할 수 있습니다.

같은 카테고리의 다른 글
CSS 강좌 / 색상 값과 px·em·rem·% 단위 이해하기

CSS 강좌 / 색상 값과 px·em·rem·% 단위 이해하기

색상 표기와 px·em·rem·%의 계산 기준을 비교합니다. 중첩된 글자 크기와 여백이 있는 상자 예제로 실제 크기를 계산하고 개발자 도구에서 확인할 수 있습니다.

CSS 강좌 / 글꼴, 글자 크기, 굵기, 줄 간격 설정하기

CSS 강좌 / 글꼴, 글자 크기, 굵기, 줄 간격 설정하기

글꼴·글자 크기·굵기·줄 간격을 설정하고 실제 사용된 글꼴을 확인하는 방법을 설명합니다. 단위 없는 line-height와 em 값의 상속 차이를 실행 가능한 예제로 비교합니다.

CSS 강좌 / position의 relative·absolute·fixed·sticky 차이

CSS 강좌 / position의 relative·absolute·fixed·sticky 차이

relative·absolute·fixed·sticky의 공간 유지, 위치 기준과 스크롤 차이를 비교합니다. 배지와 고정 버튼, 붙는 제목 예제를 실행하며 기준 부모와 동작하지 않는 원인을 확인합니다.

CSS 강좌 / 표와 셀의 크기 조절하기

CSS 강좌 / 표와 셀의 크기 조절하기

표의 자동·고정 레이아웃, 30:70 열 너비, 긴 문자열의 영향을 같은 데이터로 비교합니다. 모바일 가로 스크롤과 키보드 이동을 확인하고 말줄임 원문을 보존하는 방법을 설명합니다.

CSS 강좌 / 테두리, 둥근 모서리, 그림자 만들기

CSS 강좌 / 테두리, 둥근 모서리, 그림자 만들기

테두리와 윤곽선의 차이, 둥근 모서리, 그림자의 이동·흐림·퍼짐을 설명합니다. 원·타원과 부모 영역에서 그림자가 잘리는 상황을 실제 예제로 비교합니다.

CSS / Tutorial / 삼각형 만드는 방법

CSS / Tutorial / 삼각형 만드는 방법

CSS의 border 속성으로 삼각형을 만들 수 있습니다. 여러 형태의 삼각형을 만들고, 그 삼각형을 회전하는 방법을 알아봅니다.

CSS / Tutorial / 마우스 호버 효과 / 다른 이미지 보여주는 방법

CSS / Tutorial / 마우스 호버 효과 / 다른 이미지 보여주는 방법

이미지에 마우스를 올렸을 때 다른 이미지가 나타나는 효과를 CSS로 만들어보겠습니다. 다음과 같이 이미지 두 개를 넣고 div로 감쌉니다.

CSS 강좌 / !important

CSS 강좌 / !important

CSS에서 !important는 특정 스타일 규칙이 다른 모든 스타일 규칙보다 우선 적용되도록 강제하는 데 사용됩니다. 이 키워드는 일반적인 스타일 우선 순위를 무시하고, 해당 스타일이 다른 어떤 스타일보다도 높은 우선순위를 가지도록 합니다.

CSS / Tutorial / 문자열 자르는 방법

CSS / Tutorial / 문자열 자르는 방법

웹페이지에 출력하는 문자열의 길이는 서버단에서 조절하는 게 보통입니다. 하지만, 어떤 경우에는 그게 불가능할 수 있습니다. 이미 전송 받은 문자열을 잘라야 하는 상황이 생길 수 있습니다. CSS를 이용하여 문자열의 일부만 보여주는 방법을 소개해드립니다.

CSS 강좌 / 문단과 목록의 간격, 정렬, 목록 표시 설정하기

CSS 강좌 / 문단과 목록의 간격, 정렬, 목록 표시 설정하기

문단 안의 줄 간격, 문단 사이 여백, 첫 줄 들여쓰기를 구분합니다. 긴 목록을 이용해 outside·inside 표시 위치와 항목 간격을 비교하고 정렬 문제를 확인할 수 있습니다.