본문으로 건너뛰기
Previous
Next
CSS position 심화 | 포함 블록, 쌓임 맥락, 레이아웃 영향

CSS position 심화 | 포함 블록, 쌓임 맥락, 레이아웃 영향

CSS position 심화 | 포함 블록, 쌓임 맥락, 레이아웃 영향

이 글의 핵심

position은 좌표를 붙이는 선언이지만, 그 좌표가 어떤 사각형을 기준으로 하는지(포함 블록), 누가 위에 그려지는지(쌓임 맥락)까지 한 세트로 이해해야 합니다.

들어가며: HTML·CSS 시리즈 #04

이 글은 CSS 기초박스 모델에 이어, position 속성포함 블록(containing block), 쌓임 맥락(stacking context), 일반 문서 흐름(normal flow)과 묶어서 정리한 심화 편이다. top / right / bottom / left는 “좌표를 몇 px 이동”보다, 기준이 되는 사각형이 어디인지를 먼저 정한 뒤 읽는 편이 실무에서 오해가 적다.

아래는 본문에서 사용하는 용어를 짧게 맞춘다.

  • 일반 흐름(normal flow): static·relative가 따르는 희석되지 않은 문서의 배치 규칙(블록·인라인·플로팅은 별도).
  • 포지셔닝 박스(positioned box): positionstatic아닌 요소.
  • 초기 포함 블록(initial containing block): 뷰포트(연속 미디어)에 대응하는 최상위 기준 사각형(개념적).

1. position 개요 — CSS 레이아웃 모델에서의 위치

CSS 레이아웃은 큰 틀에서 “흐름에 둘지, 흐름 밖으로 뺄지, 스크롤에 반응시킬지”를 position으로 제어하는 경우가 많다. Flexbox·Grid는 자식의 배치 방식을 정하고, position그 흐름 안·밖좌표계에 대한 힌트를 더한다. 두 계층을 섞을 때 z-index·overflow·transform교차하면서, “코드는 맞는데 화면만 이상하다”는 현상이 자주 난다.

1.1 값과 역할(한눈에)

일반 흐름오프셋(top 등)기준(개요)
static유지무시좌표 개념 없음(초기값)
relative유지(자리는 그대로)자기 원래 위치에서 시각적 이동레이아웃이 차지하는 공간은 이동 전과 동일
absolute이탈포함 블록 기준가장 가까운 “포지셔닝 조상” 등
fixed이탈보통 뷰포트특정 조건에서 조상 좌표계에 묶임
sticky유지하다가 스티키스크롤 박스·임계값에 따라relativefixed 사이의 하이브리드

position명세 CSS Positioned Layout Module에 정의돼 있으며, 브라우저는 렌더링 파이프라인의 레이아웃 단계에서 “박스의 기준”과 “겹침 순서”를 이 규칙에 맞춰 계산한다.

2. static — 기본 흐름과 문서 순서

static포지셔닝되지 않은 기본값이다. 이 상태에서는 top / right / bottom / left·z-index효과가 없다(또는 명세에 따라 적용 대상이 아니다). 박스는 문서 순서·부모의 블록/인라인 포매팅에 따라 쌓인다.

실무에서의 인상: “아무것도 안 건드린 요소”가 static이다. 레이아웃을 absolute로 풀기 전, 먼저 부모-자식 높이·마진 병합이 예상과 같은지 확인하면 디버깅이 빨라진다. static인 형제 둘 사이의 세로 마진이 병합되지만, absolute로 빠진 자식은 그 계산에 기여하지 않는 식의 차이를 이 시리즈 박스 모델과 함께 읽는 것이 좋다.

<!-- static: 문서 순서가 곧 겹침(동일 z-index)의 기반 -->
<article>
  <p class="a">A 문단</p>
  <p class="b">B 문단</p>
</article>
/* 오프셋을 줘도 static에서는 무시되는 것이 정상 */
.a {
  position: static;
  top: 100px; /* 효과 없음 */
}

3. relative — 원래 위치를 기준으로 한 이동

relative일반 흐름에 남는다. 다만 top / right / bottom / left를 주면, 박스는 자기가 원래 있었을 자리(원래의 자리에 해당하는 점/사각형)를 기준으로 시각적으로 이동한다. 레이아웃이 예약한 공간(대개 원래의 공간)은 그대로라서, 주변 콘텐츠는 “이동 후의 실제 윤곽”이 아니라 이동 전을 기준으로 배치된다.

.item {
  position: relative;
  top: 8px;   /* '원래 있던 곳'에서 위로(일반적 관례) */
  left: 12px;
}

3.1 상세 동작(왜 “겹쳐 보이나”)

relative로 옮긴 요소는 흐름의 슬롯은 그대로 두고, 그림(페인트)만 옮긴다고 이해할 수 있다. 그래서 아래에 있던 인접 박스가 relative 요소의 이동 뒤 시각적 영역과 겹쳐 보이는 것이 흔하다. “간격이 왜 이상하지?”는 질문의 상당수가 이 원래 자리·시각적 자리의 괴리에서 출발한다.

3.2 포함 블록으로서의 relative

position: relative는 오프셋이 0이든 아니든, 자기 자손에 대해 “포지셔닝된 조상”이 될 수 있다. absolute 자식을 부모 영역 안에 맞추고 싶다면, 부모에 position: relative를 주는 앵커링(anchoring) 패턴이 널리 쓰인다(아래 [9절](#9-포함 블록-containing-block—기준-사각형-정리)과 연결).

4. absolute — 가장 가까운 positioned 조상 기준

absolute일반 흐름에서 제거된다(부모의 자동 높이 계산에 직접 기여하지 않음). top / right / bottom / left포함 블록에 대해 오프셋을 지정하며, 그 포함 블록의 후보는 대략 “가장 가까운 position: static아닌 조상” 쪽(명세 § Containing block for 절)으로 찾는다. 조상이 없다면 초기 포함 블록에 가깝게 잡힌다(연속 미디어에서 뷰포트에 대응하는 루트의 개념).

<div class="card u-anchor">
  <div class="badge u-abs">New</div>
  <h2>제목</h2>
  <p>본문</p>
</div>
.u-anchor { position: relative; } /* absolute 자식의 '기준' 후보 */
.u-abs {
  position: absolute;
  top: 10px;
  right: 10px;
}

4.1 absolute + 크기(퍼센트)

width / height에 퍼센트가 오면, 그 기준은 “임의의 부모”가 아니라 포함 블록이다. absolute의 포함 블록이 어디인지 틀리면 50%화면 절반이 아니라 엉뚱한 박스의 절반이 될 수 있다. 이는 12절의 반응형 설계에서도 핵심이다.

5. fixed — 뷰포트 기준과 고정 헤더/푸터

fixed는 일반 흐름에서 벗어나 대부분 뷰포트에 고정된 것처럼 보인다. 고정 헤더·하단 CTA·떠다니는 채팅 버튼에 자주 쓰인다.

.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  /* background, border 등으로 본문과 대비 */
}
/* 본문이 헤더 뒤로 깔리지 않도록 패딩 보정(대표 패턴) */
body {
  padding-top: 56px;
}
.site-footer-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 90;
}

5.1 transform·filter 등에 “끌려가는” fixed (중요)

통상 fixed뷰포트 기준이지만, 조상에 transform, filter, perspective, will-change: transform 등이 걸리면, 구현·명세 흐름에 따라 fixed가 그 조상의 좌표계(스택/콘텐핑) 안에 묶이는 사례가 있다. “모달 fixed 헤더가 뷰가 아니라 모달 박스에 붙는다”는 증상은 이 조합을 의심한다. 해결은 (1) transform을 모달 바깥으로 빼기, (2) 포털(React createPortal 등)로 DOM을 루트에 두기, (3) position: fixed 대신 absolute + 스크롤 래퍼 맞추기 등 케이스별로 선택한다.

6. sticky — 스크롤 임계값과 실전 활용

stickyrelativefixed절충에 가깝다. 먼저 relative처럼 흐름에 남다가, 스크롤이 일정 임계(예: top: 0)에 도달하면 fixed에 가깝게 “붙는” 동작을 한다(정확한 수학은 [CSS Position 3]의 sticky 제약). 섹션의 소제목 바, 가로·세로의 테이블 헤더, 영역 안에서만 도는 Sticky nav에 잘 맞는다.

.toc {
  position: sticky;
  top: 64px; /* 위쪽 고정 헤더가 있다면 그 높이만큼 */
}

자주 막는 조건:

  • 조상overflow: hidden / auto / scroll이 있으면, 그 조상이 스크롤 박스가 되어 기대한 “전체 페이지 기준” sticky가 아닐 수 있다.
  • sticky 요소의 감싸는 부모가 스크롤로 끝까지 가지 못하거나(높이 부족) 을 만나면, sticky도 함께 “빠진다”.

stickyFAQ의 네 번째 답(오버플로, 높이, 테이블 셀 맥락)을 함께 읽는 것이 좋다.

7. top, right, bottom, left — 오프셋의 의미

네 속성은 포함 블록의 해당 가장자리로부터, 박스의 대응하는 마진 가장자리까지의 거리(대개) 를 지정한다(명세의 정의에 따름). 동시topbottom을 지정하거나, leftright를 지정하면, width/heightauto일 때 끌어 늘려지는(stretched) 효과가 날 수 있다(명세/레이아웃 모드에 의존).

속성읽는 방향(연속 미디어, 일반적)메모
top포함 블록 가장자리 → 요소음수면 위로 “넘어감”
right포함 블록 오른쪽 → 요소RTL 환경에서 UI 설계와 함께 고려
bottom포함 블록 아래 → 요소하단 바·토스트
left포함 블록 왼쪽 → 요소사이드바·도킹 UI

inset: 0는 네 면 동시 지정(현대 CSS 축약)으로, absolute가용 영역 꽉 채우기에 쓰기 좋다.

.cover {
  position: absolute;
  inset: 0; /* top/right/bottom/left: 0 과 동일 */
}

static이 아닌 relative·absolute·fixed·sticky는 오프셋을 해석할 수 있지만(명세), 실무에서 가장 흔한 실수는 static에 오프셋을 줬다가 “왜 안 움직이지?”로 이어지는 사례다.

8. z-index — 쌓임 맥락(Stacking Context)과 겹침

z-index전역 z 순서가 아니다. 부모가 연 쌓임 맥락(Stacking context) 내부의 depth 정렬로 이해하는 것이 안전하다. 부모 맥락의 z-index가 앞서 있지(kid가 아무리 9999여도) 부모-조부의 맥락에서 밀리면, 모달·드롭다운이 헤더/배경 아래에 깔리는 “유명한” 현상이 난다.

8.1 새 쌓임 맥락(대표 조건, 비완전 목록)

  • positionstatic아님 + z-indexauto아님
  • opacity가 1 미만
  • transform / filter / perspective / isolation: isolate 등(명세 § ‘Stacking the element’ 참고)
  • Flex/Grid 자식 + z-index 조합(명세)

8.2 예: 같은 부모, 형제끼리

<div class="stack">
  <div class="a">A (아래 느낌)</div>
  <div class="b">B (위 느낌)</div>
</div>
.stack { position: relative; }
.a, .b { position: absolute; left: 0; top: 0; }
.a { z-index: 1; }
.b { z-index: 2; } /* A 위에 */

stack 바깥에 또 맥락이 있고 그쪽 z-index가 낮다면, b를 아무리 키워도 다른 맥락의 형제를 ‘뒤집는’ 효과는 기대할 수 없다(그 경우는 15절 참고).

9. 포함 블록(Containing block) — 기준 사각형 정리

[CSS2.1/Position 3]에서 containing block은 속성(박스 모델, 퍼센트, absolute·fixed 오프셋)마다 조금씩 정의가 다를 수 있어, “항상 부모 콘텐츠 박스”로 통일할 수는 없다. 대신 실무 루브릭:

  1. absolute는 가장 가까운 positioned 조상(대개 static아닌 relative / absolute / fixed / sticky ) 를 포함 블록 후보로 쓰는 흐름이 많다(명세: positioned ancestor의 패딩 박스 등).
  2. fixed뷰포트에 대응하는 초기 포함 블록에 가깝되, 5.1절transform특이 조상이 있으면 그 안이 고정의 기준이 될 수 있다.
  3. relative이동 전 자리를 공간에 남기므로, 절대 자식의 “앵커”로 쓰기 좋다.

시리즈 #04 박스 모델에서 콘텐츠/패딩/보더 박스의 차이를 익혀 두면, “왜 이 좌표가 패딩 안쪽/바깥으로 보이지?”에 대한 감이 빨리 잡힌다.

10. 실전 패턴

10.1 모달/팝업(오버레이 + 패널)

fixed + 반투명 오버레이 + 중앙 정렬이 전형적이다(스크롤 잠그기는 bodyoverflow: hidden 등은 접근성·모바일과 함께 검토).

<div class="modal" hidden>
  <div class="modal__backdrop" data-close></div>
  <div class="modal__panel" role="dialog" aria-modal="true">
    <h2>제목</h2>
    <p>내용</p>
  </div>
</div>
.modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: grid;
  place-items: center;
  padding: 16px; /* 뷰포트 가장자리와 간격 */
}
.modal__backdrop {
  position: absolute; /* .modal(=containing block) 꽉 채움 */
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
}
.modal__panel {
  position: relative; /* z-index, 포커스 링, 내부 absolute 기준 */
  z-index: 1;
  max-width: min(720px, 100%);
  max-height: min(80vh, 100%);
  overflow: auto;
  background: #fff;
  border-radius: 12px;
  padding: 20px;
}

10.2 툴팁(호버/포커스, 작은 풍선)

absolute + bottom: 100%/left: 50% + translate(-50%) 조합이 흔하다. pointer-events: none으로 겹침을 피하거나, 충분한 z-index쌓임 맥락을 염두에 둔다.

.tooltip { position: relative; display: inline-block; }
.tooltip__label {
  position: absolute;
  left: 50%;
  bottom: 100%;
  transform: translate(-50%, -6px);
  white-space: nowrap;
  z-index: 5;
  opacity: 0;
  pointer-events: none;
  transition: opacity 120ms ease;
}
.tooltip:hover .tooltip__label,
.tooltip:focus-within .tooltip__label { opacity: 1; }

10.3 드롭다운 메뉴

트리거에 position: relative를 주고, 메뉴는 absolute; top: 100%; left: 0; min-width: ...아래로 늘인다. overflow: hidden인 헤더 안에 붙이면 잘리므로(overflow 절), 필요하면 메뉴를 body 포털로 빼서 fixed + 좌표 계산(JS)으로 처리한다(대용량·모바일·키보드).

.menu { position: relative; }
.menu__list {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 20;
  min-width: 200px;
  background: #fff;
  box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}

10.4 Sticky 헤더/사이드바

  • Sticky 헤더5절 fixed와 경쟁. 스크롤 구간에서만 강조하고 싶다면 sticky가, 항상 보이게 하면 fixed.
  • 사이드바position: sticky; top: 0; + align-self: start그리드/플렉스에선 자주 쓰인다(부모 align-items: stretch의 함정 방지). 부모 overflow를 점검할 것(6절).

10.5 Fixed 네비게이션(모바일 하단 탭)

fixed; bottom: 0; + safe-area-inset을 함께 쓰면 iOS 홈 인디케이터와 겹침이 줄어든다. 본문에 padding-bottom으로 빈 공간을 확보한다.

.nav-bar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 200;
  padding-bottom: max(8px, env(safe-area-inset-bottom, 0px));
  background: #fff;
  border-top: 1px solid #e5e5e5;
}

11. 레이아웃 조합 — position + Flexbox/Grid

Flex/Grid는 배치 알고리즘이고, position: absolute 자식은, 그 flex/grid 아이템으로서 배치될 수 있지만(명세) 별도의 position 규칙(포함 블록)이 같이 돈다. 실무 루브릭:

  • Grid 셀 안absolute 자식을 “셀 꽉 채움”에 쓰는 패턴(오버레이, 배지)은, 부모(셀)에 position: relative를 주고 자식 inset: 0일치시킨다.
  • Sticky 사이드바grid-template-columns: 240px 1fr; + 오른쪽 열 min-width: 0;(오버플로)와 같이 Flexbox/Grid 시리즈의 제약(최소 폭)과 함께 본다.
.layout {
  display: grid;
  grid-template-columns: 260px 1fr;
  gap: 24px;
  align-items: start;
}
.sidebar { position: sticky; top: 12px; }
.main { min-width: 0; } /* flex/grid child overflow */

12. 반응형 position — 미디어 쿼리와 조합

작은 뷰포트에선 fixed 헤더를 sticky완화하거나, bottom: 0 내비를 끄고 top 햄버거로 전환하는 식이 흔하다. position 자체는 대개 그대로 두고, 높이/패딩/가시성을 미디어 쿼리로 토글한다.

@media (max-width: 640px) {
  .site-header {
    position: sticky; /* 모바일에선 '전역 고정' 부담 감소 */
    top: 0;
  }
  body { padding-top: 0; } /* fixed 전용 본문 보정 제거 */
}

env(safe-area-inset-*)notch/홈 대응에 유용. dvh/svh 등 뷰포트 단위는 모바일 주소창 출입에 따른 가시 높이 변화를 줄이는 데 쓰인다(주제: 반응형).

13. 성능 고려 — reflow(레이아웃) / repaint(페인트)

브라우저는 스타일 변경 시 Layout → Paint → Composite(엔진 별로 단계명은 다를 수 있음)로 처리한다. top / left 직접 애니메이션은 layout을 자주 일으킬 수 있고, transform / opacity는 합성 단계에 맡기는 편이 스무스하다.

변경러닝(대략)권장
left / top / width / height / font-size레이아웃·리페인트가능하면 피하거나, 드문 레이아웃으로
transform / opacity합성 위주(일반적)이동·페이드에 우선
z-index맥락/레이어에 따라 다름불필요한 맥락·큰 z-index 남용 자제
filter / clip-path (무거운 경우)비용↑케이스별 프로파일

will-change: transform잘 쓰면 이득, 남발하면 메모리·맥락 부작용(5.1절의 fixed 끌림)이 생긴다. 꼭 필요한 요소한시적으로 제한하라.

14. 브라우저 호환성 — Sticky 위주

position: sticky는 최신 데스크톱·모바일 대부분에서 실사용이 가능하다(구형 IE는 지원 불가). 그래도:

  • overflow: hidden 조상·높이 0 부모·테이블 셀 등 레이아웃 맥락에서 “안 붙는” 것이 버그가 아니라 조건인 경우가 많다.
  • Safari는 prefix -webkit-sticky가 과거엔 쓰였고, 현대 프로젝트는 대개 표준 sticky로 충분(팀의 타겟 범위에 따라 Can I use 확인).

fixediOS 과거 position: fixed + 키보드/주소창 조합의 이슈가 잘 논의됐다(현재도 기기/버전·뷰포트 단위와 함께 QA 권장).

15. 일반적 문제 — z-index가 “안 먹는” 이유, 부모-자식

  1. 비교군이 다름: z-index같은 쌓임 맥락의 형제(또는 맥락 규칙이 허용한 범위)끼리. 부모-삼촌이 다른 맥락이면 자식 9999삼촌의 1에도 못 이긴다(부모의 맥락 먼저).
  2. position: static + z-index: z-indexstatic에서 쌓임 맥락 생성 조건이 다르다(맥락은 여전히 다른 이유로 생긴다). relative+z-index명시하는 편이 직관적.
  3. 새 맥락을 만드는 opacity / transform / filter: 의도치 않은 최소 맥락이 생겨 순서가 뒤집힌 것처럼 보인다.
  4. 부모 overflow: hidden: absolute 메뉴/툴팁이 잘려 “z는 맞는데 안 보인다”로 착각.

절차: (1) DevTools로 쌓임 맥락 추적(아래 16절) (2) 막는 조상 overflow / transform / filter (3) 포털로 루트에 모달/메뉴 (4) isolation: isolate맥락 의도 명시(감팀·디자인 시스템).

16. 디버깅 팁 — DevTools 활용

  • Elements(요소): position / z-index / offset / insetLive로 바꿔 포함 블록이 바뀌는지 확인.
  • Computed(계산됨): position최종 값(상속/미디어쿼리) 추적. z-index: auto인지.
  • Layers(레이어): Chrome 등에서 합성 레이어/겹침 시각화(버전/플래그에 따라).
  • Layout: Grid/Flex 오버레이앵커 셀·트랙 확인.
  • 3D view / stacking(제공 시): z 겹침 직관 확인(실험 기능일 수 있음).

작은 절차: “위에 오게”가 목표면, 먼저 부모-조부까지 z-index·transform·opacity아래→위로 훑는다. 전역 9999 대신 계층 설계(디자인 토큰): z-header, z-dropdown, z-modal 같은 명명이 유지보수에 유리하다.

17. Best Practices — 체크 테이블

주제권장지양(또는 주의)
absolute 앵커조상 position: relative명시암묵적 body앵커(대형 페이지)
쌓임z 레벨 체계 + 최소 transform/opacityz-index·맥락 남발
Sticky스크롤 부모·overflow·top테이블/알 수 없는 overflow
fixed뷰포트 고정 UItransform 조상과의 끌림 미확인
애니메이션transform / opacityleft/top 고빈도 트윈
반응형미디어쿼리로 모드 전환모바일에서 fixed 남용
접근성모달 포커스 트랩, 스크롤 락 검토키보드로 도달 불가
iOS/안드dvh/env(safe-area...)안전 영역 무시

실무 한 줄: 포지션을 바꾸면, 함께 읽을 것은 포함 블록·쌓임 맥락·overflow 세 가지다.

17.1 inset·논리 속성(Logical properties) — 다국어·RTL

top / right / bottom / left물리 방향(위·오·아·왼)이다. 다국어·RTL(우→좌) UI에서는 inline/block 축으로 쓰는 논리 속성이 읽기 쉽다.

물리논리(요약)비고
left / rightinset-inline-start / inset-inline-endLTR/RTL에서 시작·끝이 뒤바뀜
top / bottominset-block-start / inset-block-end수직 쓰기 모드에서 축이 달라질 수 있음
inset: 0inset-inline: 0; inset-block: 0; (개념적)축약은 상황에 맞게

position: absolute + 논리 inset국제화(i18n) 팀·디자인 시스템에서 점차 표준이 되고 있으며, “왼쪽 고정=항상 LTR” 가정이 깨지는 아랍어·히브리어 레이아웃에서 실수를 줄인다. 물리 속성이 틀린 것은 아니다. 제품이 RTL을 지원한다면, 트리거·드롭다운·사이드 패널의 left: 0 일괄이 아니라, start/endtransform + logical로 검토할 가치가 있다.

17.2 Sticky — 스크롤 박스(scrollport) 개념도

sticky는 “뷰포트에 붙는다”는 감이 있지만, 실제는 가장 가까운 스크롤을 만드는 조상의 스크롤 박스에 대해 top / left 임계를 만든다(명세: sticky positioning). overflow: auto 부모가 있으면 그 박스 안에서만 붙는다.

flowchart TB
  subgraph V["뷰포트(또는 루트 스크롤)"]
    subgraph S["section (overflow: visible)"]
      subgraph P[".panel (overflow: auto ← 스크롤 박스)"]
        C[".sticky (position: sticky; top:0)"]
        T["(긴 콘텐츠)"]
      end
    end
  end
  • 뷰 스크롤만 쓰는 문서: .panel이 없다면(대개) 루트 스크롤이 스크롤 박스로 인식돼, 기대한 “페이지 Sticky”에 가깝다.
  • 카드/패널 안만 스크롤: Sticky는 그 패널의 상단(또는 top에 맞춘 축)에 “붙는” 것이 정상이다.

부모 높이가 Sticky 요소보다 짧으면(부모에 남는 스크롤 거리가 없다면), Sticky는 끝까지 가기 전에 같이 스크롤되어 사라질 수 있다(“잠깐 붙다가” 사라짐). 이는 버그가 아니라, 끝(클램프) 조건에 가깝다.

<div class="section">
  <h3 class="section__head">이 섹션의 제목</h3>
  <div class="section__body">…</div>
</div>
.section { /* 충분한 세로 스크롤 “거리”를 확보 */ }
.section__head {
  position: sticky;
  top: 0;
  background: #fff; /* 뒤 콘텐츠와 겹칠 때 읽힘 */
  z-index: 1;
  padding: 8px 0;
  border-bottom: 1px solid #eee;
}

17.3 페인팅(painting) 순서z-index — 7·8단 맥락(개념)

z-index는 “큰 수가 위”가 전제이지만, 쌓임 맥락이 트리처럼 겹쳐 있으면 맥락끼리 먼저 맞는다. CSS 2.1의 Appendix E끌어올리기(Appendix E)·[CSS Position 3]의 “layer” 설명이 근거이다. 실무에선 맥락을 “상자”로 생각한다.

  1. 맥락은 부모-자식이 아니라, 같은 스택형제끼리 z를 비교한다(단, auto/레이어 규칙이 있음).
  2. position+z-index맥락이 생기면, 그 안의 자손 z다른 맥락섞이지 않는다(비유: 여권 구역).
(보다 뿌리에 가까운) 맥락 A
  ├─ z=0: 헤더(맥락 내부 자식)
  └─ (자식 A1의 z=10000은) …동일 맥락 내에서만 최상위

(형제) 맥락 B (z=1)
  ├─ 본문
  └─ (맥락 A의 A1=10000은) … B 전체(맥락)보다 "아래"일 수 있음(부모 맥락 z가 정함)

17.4 Clip·Mask·contain 과 겹침(보충)

clip-path, mask, overflow: clip클리핑가시/히트(클릭) 영역에 영향을 주며, Sticky/absolute가 “보이지 않는” 이유로 z와 혼동되기 쉽다(특히 overflow: hidden 헤더). DevTools Layers·3D 뷰로 “잘렸다”는 사실이 먼저인지, “z에 졌다”는 사실이 먼저인지 분리하라. content-visibility·contain: paint최적화페인트 경계에 영향을 주므로, 툴팁/드롭다운갑자기 잘리면 containment도 의심한다.

17.5 접근성position키보드·리더 모달, 드롭다운, 툴팁

  • 드롭다운: absolute / fixed + aria-expanded, aria-controls, 포커스가 리스트로 이동하는지(로밴) 확인.
  • 모달: role="dialog", aria-modal="true", 포커스 트랩, Esc로 닫기(팀 합의), 스크롤 락은 스크롤이 튀는 iOS/안드 QA.
  • 툴팁: pointer-events: none가리기에 좋지만, 롱-프레스·스크린 리더 대안(aria-describedby / 별도 접근)을 디자인과 합의.
<div class="menu">
  <button class="menu__btn" type="button" aria-expanded="false" aria-controls="m1" id="m1-b">
    메뉴
  </button>
  <ul class="menu__list" id="m1" role="menu" hidden>
    <li role="menuitem"><a href="/a">항목 A</a></li>
    <li role="menuitem"><a href="/b">항목 B</a></li>
  </ul>
</div>
.menu { position: relative; }
.menu__list[hidden] { display: none; }
.menu__list {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 30;
  min-width: 220px;
  background: #fff;
}

(열고 닫는 토글·포커스 전진·aria-expanded 갱신은 JS가 맡는다 — 본문 범위를 넘어가므로 구조만 맞춘다.)

17.6 인쇄(@media print)fixed / sticky

fixed 요소(헤더·광고·내비)는 인쇄에서 모든 페이지에 반복되거나(구현·브라우저), 쓸모없이 낭비될 수 있다. @media print에서 .no-printdisplay: none, Sticky/Fixed를 해제하거나 static으로 되돌리는 팀이 많다.

@media print {
  .site-header, .nav-bar, .ad-slot {
    position: static !important; /* 필요 시 none 대신 */
  }
  body { padding-top: 0 !important; }
}

17.7 그리드·플렉스에서 absolute 오버레이 — 풀 예제(카드)

그리드 셀·플렉스 아이템채우는 absolute 자손은, 부모(셀)에 position: relative를 둬 포함 블록을 고정한다. 비율aspect-ratio + object-fit이 자주 쓰인다.

<article class="card">
  <a class="card__link" href="/p/1">본문은 링크</a>
  <img class="card__img" src="hero.webp" alt="" width="800" height="500" />
  <span class="card__badge">BEST</span>
</article>
.card {
  position: relative;
  display: block;
  border-radius: 12px;
  overflow: hidden;
  aspect-ratio: 16 / 9;
  background: #f3f3f3;
}
.card__img { width: 100%; height: 100%; object-fit: cover; display: block; }
.card__link {
  position: absolute; inset: 0; z-index: 1;
  /* 콘텐츠 위 클릭: 텍스트는 시각적 숨김 또는 별도 요소 */
  font-size: 0;
}
.card__link::after { content: ""; position: absolute; inset: 0; }
.card__badge {
  position: absolute; top: 10px; right: 10px; z-index: 2;
  background: #111; color: #fff; font-size: 12px; padding: 4px 8px; border-radius: 999px;
}
  • overflow: hidden + border-radius: absolute 배지는 둥근 모서리 에 잘린다(의도). 반대로 드롭다운을 같은 헤더에 두면 잘림이 된다(10.3절).
  • 링크inset: 0이면, 이미지·배지z를 쪼개지 않으면 배지 클릭이 링크로 가는지 QA한다.

17.8 성능(보충)requestAnimationFrame + 레이아웃 스로틀

scroll / resize 리스너에서 top / left자주 바꾸면(팝오버 follow), layout thrashing(읽기·쓰기 교차)이 난다. 따라다니는 fixed UI는, 가능하면 transform: translate3d합성하거나, Popper·Floating UI뷰포트 클램프를 쓰고, 스크롤 루프는 rAF로 묶는다(프레임 단위 1회).

// 개념: rAF로 위치 갱신을 한 프레임에 합친다(실제는 라이브러리 권장)
let raf = 0;
function onScroll() {
  if (raf) return;
  raf = requestAnimationFrame(() => {
    raf = 0;
    // next: getBoundingClientRect() 한 번, 그 다음 스타일 한 번(가능한 한)
  });
}
window.addEventListener("scroll", onScroll, { passive: true });

17.9 브라우저 지원(발췌) — Sticky / inset / dvh

기능대략적 지원(2026년 전후, 팀의 타겟에 따라 Can I use 확인)
position: sticky (표준)모던 브라우저 광범위; IE 없음
insetChromium/Safari/Firefox 현행
dvh / svh / lvh모바일 뷰포트 이슈 완화에 유효; 반응형과 병행
-webkit-sticky과거 Safari; 신규는 표준 sticky + 폴리필(필요 시)

position: -webkit-sticky레거시 코드베이스·내부 툴에서만 우선 고려한다. 신규는 표준+테스트.

17.10 문제·트리플 체크 — “안 보임 / 안 붙음 / 잘림”

증상1차 의심2차 의심
Sticky 안 붙음overflow 조상부모 높이·display: table-cell 맥락
fixed가 뷰가 아님transform 조상filter / perspective / will-change
z 최댄데 아래다른 쌓임 맥락부모 z / opacity / isolation
메뉴 하단 잘림overflow: hidden뷰포트포털·fixed
인쇄에 광고만 반복@media print 누락벤더별 끄기

17.11 DevTools(보강) — “왜 잘렸는지 / 왜 뒤에 있는지”

  • Emulation: 모바일·RTL·접힌 주소창(뷰포트)에서 fixed/dvh/safe-area 동시 검증.
  • Scroll: Sticky 스크롤 박스를 찾을 때, Elements에서 overflow 아닌 visible 조상을 거슬러 올라 auto/scroll을 찾는다(그 박스가 스크롤 박스).
  • Performance(프로파일): Layout 시간크다면, 애니메이션left/top인지 확인(13절).
  • Accessibility: aria-* / contrast / tab order — 위치는 맞는데 포커스엉뚱한 곳(모달)으로 가는지.

18. position 값별 동작(원문 요약 테이블)

일반 흐름오프셋(top 등)기준(개요)
static유지무시초기값. 좌표 개념 없음
relative유지(자리는 그대로)자기 원래 위치에서 이동시각적만 이동, 레이아웃이 차지하는 공간은 이동 전과 동일
absolute이탈포함 블록 기준가장 가까운 포지셔닝 조상 등
fixed이탈보통 뷰포트단, 특정 조건에서 조상에 묶임
sticky유지하다가 스티키스크롤 컨테이너·임계값에 따라relativefixed 사이의 하이브리드

19. 일반 흐름 이탈과 높이/스크롤(보충)

absolute / fixed는 문서 흐름에서 제거되므로, 부모의 자동 높이가 “떠 있는” 자식만으로는 늘지 않는다가 기본. 세로 스크롤 영역·빈 래퍼·겹침 문제의 상당수가 이 규칙에서 출발한다. 해결책은 (1) 흐름에 플레이스홀더 둔다, (2) 부모에 고정/최소 높이를 준다, (3) Flex/Grid로 칸을 만든 뒤 그 칸을 기준으로 absolute를 겹친다.

19.1 마진 병합position (보충)

박스 모델에서 다루듯, 인접 블록의 세로 마진은 병합될 수 있다. 포지셔닝된 요소(relative / absolute / fixed / sticky)는, 맥락에 따라 병합이나 BFC와의 상호작용이 달라진다(명세: float·absolute·etc.). 실무 루브릭:

  • absolute / fixed: 흐름에서 빠지므로, 형제 블록세로 마진 병합끼지 않는다고 보면 대략 맞다(“왜 margin이 붙는 것처럼 보이지?”의 원인이 흐름이 아닌 다른 요소일 수 있음).
  • relative: 흐름에 남으므로, 이동 전 슬롯을 기준으로 병합 논의가 가능하다(시각은 옮겼지만, 공간·병합은 다른 규칙).
  • 부모-첫/마지막 자식 병합(부모-자식 collapse): overflow·padding·border·BFC 트릭(예: display: flow-root)이 섞이면 예상과 다를 수 있다. “absolute 자식 있는 부모 높이 0”은 19절 본문이 설명한 것과 합쳐 읽는다.

19.2 Float(플로트)absolute (역사·실무)

float는 원래 문단 옆에 그림을 두기 위한 메커니즘이다. absolute흐름을 빼앗는다. 둘 다 옛날 2·3칼 레이아웃에 쓰였지만, 현대Flex/Grid주류다. 여전히 CMS·WYSIWYG HTML에 float: left남아 있을 수 있고, 레이아웃에 absolute 오버레이를 씌울 때 float BFC엉킴이 생긴다면, 콘텐츠 루트::after{clear: both}(클리어픽스) 대신 플렉스/그리드담는 편이 낫다(유지보수).

/* 레거시 래퍼를 건드리지 않을 때(임시) */
.legacy-float-root::after { content: ""; display: table; clear: both; }

19.3 @supports로 Sticky 폴백(선택)

sticky광범위하게 쓰는 환경이면(내부 레거시 WebView), 없을 때relative+top 시각만, 있을 때 sticky다르게 줄 수 있다(팀·타겟에 따라).

.section__head--sticky {
  position: relative; /* 기본(폴백) */
}
@supports (position: sticky) {
  .section__head--sticky { position: sticky; top: 0; }
}

19.4 relative + 음수 오프셋 + 겹침(태그, 배지)

top: -4px; left: 8px; 처럼 음수는 “원래 자리”에서 밖으로 나가게 그린다(시각). 콘텐츠·다른 카드와 겹치면 z-index·맥락이 필요하다(8절). 접근성상, 최소 44px·명도 대비UI 합의와 맞출 것.

.tag {
  position: relative;
  top: -2px; /* "카드 밖"으로 약간 튀기기(시각) */
  display: inline-block;
  margin-left: 4px;
  z-index: 1; /* 겹침: 형제/부모 맥락 확인 */
}

19.5 테이블 내부 sticky / position

<table>·display: table·캡션·콜그룹렌더링특수해서, Sticky·absolute동작div 레이아웃다를 수 있다(명세/브라우저 버전). 가능하면 table div 래퍼감싸 Sticky/오버레이를 쓰거나, CSS Tables 이슈·caniuse지원을 확인한다. 핀(pin) 컬럼/로우position: sticky(가로·세로 축) 혹은 가상화·라이브러리+ARIA 데이터 그리드 패턴을 쓰는 이 많다.

19.6 position: revert / revert-layer (빠른 메모)

캐스케이드 레이어(@layer)나 써드파티 CSS와 일 때, 초기화·의도 복원에 revert·revert-layer를 쓰는 경우있다(주제: CSS 기초·명세 CSS Cascade). position: revertUA사용자작성자 으로 “원래” 값이 돌아간다(단순 unset다름). 디자인 시스템 오버라이드 경계에서 쓸 있다.

20. relative·absolute·fixed·sticky의 포함 블록(다시)

  • absolute의 포함 블록은 대략: 조상 중 static 아닌 것의 패딩 박스(흐름) 등. 없으면 초기 포함 블록가깝다.
  • fixed주로 뷰포트, 단 5.1절 참고.
  • relative이동해도 원래 공간을 남기며, absolute 자식의 앵커가 될 수 있다.

21. z-index — 그래서 자주 생기는 문제(재정리)

  • 모달이 헤더 아래에 깔임: 서로 다른 쌓임 맥락 + 부모 z가 낮음.
  • z-index: 9999인데 무시: 비교군이 형제/같은 맥락 아님.

디버깅은 DOM을 올라가며 opacity / transform / isolation / z-index: auto 여부로 “맥락이 새로 열렸는지”를 추적한다(8절).

22. 실전 체크리스트(빠른 복습)

  1. absolute를 쓴다 → 포함 블록을 만들 조상position: relative 등을 명시했는가.
  2. z-index가 안 먹는다같은 쌓임 맥락인지, 부모 맥락 z고 있지 않은지.
  3. fixed가 이상하다 → 조상 transform / filter / will-change 없는지.
  4. sticky가 안 붙는다 → 중간 overflow / 부모 높이 / 테이블 맥락 없는지.
  5. 높이가 0 → 흐름에서 빠진 자식만 없는지.

23. position + 성능(간단 요약)

fixed 헤더·sticky합성 이점이 있을 수 있으나, 과도한 z-index·will-change·불필요한 레이어는 메모리를 쓴다(13절). 스크롤·드래그에선 transform: translate3d(0,0,0) 류의 핵은 절제쓰면 5.1절 fixed 끌림 부작용재확인한다.

24. 정리

  • 포함 블록은 좌표·퍼센트·오프셋의 기준이며, absolute / fixed일반 블록과 정의가 다름.
  • z-index는 쌓임 맥락 내부의 순서이며, opacity·transform·isolation 등과 한 세트로 읽어야 한다.
  • 흐름 이탈부모 높이·마진 병합·스크롤 영역을 함께 바꾼다(박스 모델 글 병행).

이어 Flexbox·CSS Grid에서 아이템z-index·position 상호작용을 이어가면, 컴포넌트 단위로 레이아웃·겹침·스크롤을 한 번에 설계하기 쉬워진다.

25. 참고·용어(이 글에서의 사용)

용어짧은 정의(이 글 맥락)
초기 포함 블록연속 미디어에서, 루트에 대응하는 최상위 기준(뷰포트 개념과 연결)
스크롤 박스(scrollport)overflow: auto/scroll 등으로 스크롤이 생기는 박스의 가시 영역
포지셔닝(positioned)static 아닌 position
쌓임 맥락z-index·투명도·transform 등으로 한 겹 겹침 순서가 정해지는 맥락
합성(Composite)페인트된 텍스처를 GPU 등으로 겹쳐 표시하는 단계(엔진별 명칭 상이)

바깥으로 더 읽을 명세·문서:

25.1 “어떤 값을 켤까?” 빠른 선택(실무 루브릭)

목적먼저 떠올릴 값곁들일 것
문서 순서만, 좌표 불필요static(기본)
살짝 밀기, 흐름 유지relative음수 오프셋·z-index(맥락)
부모 칸 안에 붙이기(배지, 오버레이)absolute + 조상 relativeinset·퍼센트(포함 블록)
뷰포트항상fixedenv(safe-area-*)·dvh·5.1 transform
스크롤 구간에서만 “붙는” 느낌sticky스크롤 박스·top·부모 높이
맨 앞에 겹침(모달·드롭다운)(위) + z-index쌓임 맥락·isolation·8·15절

이 표는 절대 규칙이 아니라, 팀에서 UI 키트를 정리할 때 문서에 한 장 붙여 두기 좋은 초안이다.

25.2 엔진 관점 한 문장(용어로만)

레이아웃 엔진은 스타일·DOM에서 “이 박스는 흐름에 있는가, 좌표만 바뀌는가, 떨어지는가”를 position으로 분기한 뒤, 스크롤·클리핑·페인팅(레이어) 파이프라인에 넣는다. 그래서 같은 width: 100%라도 퍼센트의 기준이 포함 블록에 따라 달라지는(9절) 현상이 나온다.

  • 흐름에 남는 박스는 다음 형제·부모 높이·쌓임 맥락에 주로 기여한다.
  • 흐름에서 떨어지는 박스(absolute / fixed)는 형제의 자리를 빼앗지 않는 대가로, 부모의 자동 높이에도 끼지 않는다(19절).
  • 둘을 섞는 UI(카드+배지)는 9·10·11절의 “앵커, 오버레이, z”를 한 묶음으로 읽으면 실수가 줄어든다.

이 절의 세 줄은 위 긴 본문을 한 화면에 압축한 메모다. (시리즈 #04 position 핵심 복기에 쓰기 좋다.) 명세·엔진의 모든 분기를 외울 필요는 없고, “흐름/좌표/떨어짐”으로 먼저 보면 나머지는 표와 절차(17·22절)에 자연스럽게 수렴한다.

팀 온보딩용으로, 이 글 19절을 하루, 1017절을 하루로 끊어 읽는 일정을 잡는 것도 한 방법이다. (독서 시간 readingMinutes는 frontmatter에 맞춰 두었고, 본문을 확장한 뒤에는 실제 체감 시간이 더 길 수 있다.)

26. 시리즈 #04 — 배운 뒤 바로 해볼 만한 실습(자가 점검)

아래는 필수 과제가 아니라, 이해를 손으로 확인하는 자가 점검 절차다.

  1. 최소 HTML로 header(고정)와 긴 main을 만든 뒤 header에만 position: fixed를 적용한다. 스크롤할 때 본문 첫 줄이 헤더 뒤에 가리는지 본다. bodypadding-top을 얼마로 두면 겹침이 사라지는지 수를 잡는다(5절).
  2. 같은 DOM에서 header 내부 래퍼에 transform: translateZ(0)(또는 will-change: transform)을 준 뒤, 그 안에 position: fixed 버튼을 두고 뷰포트 고정이 깨지는지 확인한다(5.1절).
  3. overflow: auto인 카드 안에 position: sticky 제목을 두고, 카드만 스크롤할 때와 전체 페이지를 스크롤할 때 동작이 어떻게 달라지는지 비교한다(6·17.2절).
  4. 형제 두 박스에 z-index를 다르게 준 뒤, 부모에 opacity: 0.99 한 줄을 추가했을 때 앞뒤 순서가 뒤집히는지(쌓임 맥락) 관찰한다(8·15절). 끝나면 opacity는 원래대로 되돌린다.

이 네 가지는 DevTools의 Elements, 스크롤, 가능하면 Layers 패널만으로 충분하다. 팀에 공유할 때는 스크린샷 한 장에 “포함 블록과 쌓임 맥락이 어떻게 달랐는지” 한 문장만 덧붙여도 복기에 도움이 된다.

관련 글

  • CSS 박스 모델 | Margin, Padding, Border 완벽 정리
  • CSS 기초 | 선택자, 속성, 색상, 폰트
  • CSS Flexbox | 플렉스박스 레이아웃 완벽 가이드
  • 반응형 웹 디자인 | 미디어 쿼리와 모바일 최적화

자주 묻는 질문 (FAQ) — 본문 보강

Q. 이 포스트는 어떤 순서로 읽으면 좋나요?

A. CSS 기초박스 모델이 글(position)Flexbox 순이 자연스럽다. position은 Flex/Grid와 겹쳐 쓰일 때 읽는 시야가 넓어진다.

Q. z-index: 9999는 왜 “안 먹는” 것처럼 보이나요?

A. 전역이 아니라 부모 쌓임 맥락 안의 값이다. 8절·15절을 보며 조상z-index·transform·opacity를 함께 점검하라. 프런트엔드 시스템에선 z 토큰으로 단계를 관리하는 팀이 많다.

Q. Sticky는 어디에 쓰는 게 좋고, fixed와 어떻게 나누나요?

A. sticky스크롤 구간 안에서 “일정 위치”에 들러붙이기, fixed항상 화면의 한 축에 고정할 때(단, 5.1 transform 주의)다. 6·10.4·14절을 참고한다.


이 글에서 다루는 키워드 (검색·내부 링크)

CSS, position, static, relative, absolute, fixed, sticky, top/right/bottom/left, inset, z-index, stacking context, containing block, normal flow, overflow, transform, Flexbox, Grid, 미디어 쿼리, reflow, repaint, scrollport, safe-area 등.