웹 개발을 하다 보면 요소의 위치를 조정해야 할 일이 수없이 많다. 그런데 CSS position 속성을 정확히 이해하지 못한 채 top, left, right, bottom 값만 무작정 조정하다가 레이아웃이 엉망이 되는 경험을 누구나 한 번쯤 해봤을 것이다. 실무에서는 position의 종류에 따라 요소가 배치되는 기준점(containing block)이 완전히 달라지는데, 이를 모르고 있으면 z-index 꼬임, 예상과 다른 위치 배치, 스크롤 시 겹침 문제 등이 계속 반복된다. 이번에는 position의 각 값이 정확히 무엇인지, 언제 어떤 값을 써야 하는지, 그리고 자주 실수하는 경우들을 완벽하게 정리해서 소개하겠다.
| 값 | 설명 | 기준점(containing block) | 문서 흐름 |
|---|---|---|---|
| static | 기본값. 위치 이동 불가 | 부모 요소 | 정상 흐름(flow) |
| relative | 자신의 원래 위치 기준으로 이동 | 자신의 원래 위치 | 정상 흐름 유지 |
| absolute | 가장 가까운 position이 static이 아닌 부모 기준으로 이동 | 부모의 padding box | 흐름에서 제거 |
| fixed | 뷰포트 기준으로 고정 | 뷰포트 | 흐름에서 제거 |
| sticky | 스크롤되면 fixed처럼 고정(최신 브라우저) | 스크롤 컨테이너 | 정상 흐름 유지 |
모든 요소의 기본값이다. top, left 같은 위치 속성을 아무리 줘도 작동하지 않는다. 정상적인 문서 흐름을 따른다.
<!-- ✗ 잘못된 방법 -->
<div style="position: static; top: 50px; left: 50px;">
위치가 이동하지 않는다
</div>
<!-- ✓ 올바른 방법 -->
<div style="position: relative; top: 50px; left: 50px;">
위치가 50px 아래, 50px 오른쪽으로 이동한다
</div>
자신의 원래 있던 위치를 기준으로 이동한다. 중요한 점은 원래 차지하던 공간은 여전히 예약되어 있다는 것이다. 즉, 문서 흐름에 영향을 주지 않으면서 시각적으로만 이동한다.
<!-- ✗ 잘못된 경우: absolute의 기준점이 될 부모를 만들려고 static인 부모 사용 -->
<div style="position: static;">
<div style="position: absolute; top: 0; left: 0;">
이 요소는 부모를 무시하고 body를 기준으로 배치된다
</div>
</div>
<!-- ✓ 올바른 방법: relative를 부모로 주기 -->
<div style="position: relative;">
<div style="position: absolute; top: 0; left: 0;">
이제 이 부모를 기준으로 배치된다
</div>
</div>
가장 자주 오용되는 값이다. position이 static이 아닌 가장 가까운 부모 요소를 기준으로 배치된다. 부모가 없으면 body(정확히는 document)를 기준으로 한다. 절대 위치 요소는 문서 흐름에서 완전히 제거되므로 다른 요소가 그 자리를 채운다.
<!-- ✗ 흔한 실수: 부모가 static인 경우 -->
<div class="parent" style="position: static;">
<div class="child" style="position: absolute; top: 10px; left: 10px;">
부모의 왼쪽 10px 아래에 올 거라 생각하지만
실제로는 화면(body) 기준으로 배치된다
</div>
</div>
<!-- ✓ 올바른 방법 -->
<div class="parent" style="position: relative;"> <!-- 부모에 relative 반드시 필요 -->
<div class="child" style="position: absolute; top: 10px; left: 10px;">
이제 부모 기준으로 정확히 배치된다
</div>
</div>
뷰포트(보이는 화면)를 기준으로 고정된다. 스크롤해도 움직이지 않는다. 네비게이션 바, 플로팅 버튼 같은 요소에 쓰인다. 부모의 position 값에 무관하게 항상 뷰포트 기준이다.
<!-- ✗ 잘못된 경우: transform이 있는 부모 하위에 fixed 배치 -->
<div style="transform: translateZ(0);">
<div style="position: fixed; bottom: 0; right: 0;">
transform이 있는 부모가 새로운 stacking context를 만들어서
fixed가 실제로는 fixed처럼 작동하지 않을 수 있다
</div>
</div>
<!-- ✓ 올바른 방법: body 바로 하위에 배치 -->
<div style="position: fixed; bottom: 0; right: 0;">
플로팅 버튼
</div>
sticky는 상대적으로 최신 기능이다(IE 미지원). 처음에는 relative처럼 작동하다가 스크롤되면 fixed처럼 고정된다. 부모 요소 범위 내에서만 고정되므로 테이블 헤더, 섹션 제목 같은 요소에 유용하다.
<!-- ✗ 잘못된 경우: sticky가 작동하지 않는 구조 -->
<div style="overflow: hidden;"> <!-- overflow 속성이 새 stacking context 생성 -->
<div style="position: sticky; top: 0;">
스크롤 범위가 제한되어 sticky가 제대로 작동하지 않음
</div>
</div>
<!-- ✓ 올바른 방법 -->
<div>
<div style="position: sticky; top: 0;">
테이블 헤더처럼 스크롤하면 고정됨
</div>
<div>스크롤 콘텐츠</div>
</div>
부모 기준으로 정가운데 배치하려면 부모가 relative여야 한다. 많은 개발자가 이를 빼먹고 top, left 값만 조정한다.
/* ✗ 잘못된 코드 */
.parent {
width: 400px;
height: 400px;
/* position이 없음 = static */
}
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
/* 부모가 static이므로 body 기준으로 배치됨 */
}
/* ✓ 올바른 코드 */
.parent {
position: relative; /* 이것이 핵심 */
width: 400px;
height: 400px;
}
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
/* 이제 부모의 정가운데에 배치됨 */
}
fixed는 뷰포트 기준이므로 부모의 width 제약을 받지 않는다. 부모가 max-width를 가진 컨테이너라면 fixed 자식이 화면 전체로 확장된다.
<!-- ✗ 문제 상황 -->
<div style="max-width: 1200px; margin: 0 auto;">
<div style="position: fixed; bottom: 0; left: 0; right: 0; height: 50px;">
화면 전체 너비의 플로팅 바
</div>
</div>
<!-- ✓ 해결 방법: fixed 요소를 부모 밖으로 이동 -->
<div style="max-width: 1200px; margin: 0 auto;">
콘텐츠
</div>
<div style="position: fixed; bottom: 0; left: 0; right: 0; height: 50px;">
플로팅 바를 별도로 배치
</div>
sticky가 잘 작동하려면 부모 요소가 overflow: visible이어야 하고, 부모의 높이가 sticky 요소보다 커야 한다. overflow: hidden이나 overflow: auto가 있으면 sticky의 범위가 제한된다.
/* ✗ sticky가 작동하지 않는 경우 */
.container {
overflow: auto; /* 문제: 이 컨테이너 내에서만 sticky 고정됨 */
height: 200px;
}
.sticky-header {
position: sticky;
top: 0;
}
/* ✓ 올바른 구조 */
body {
overflow: visible; /* 기본값 */
}
.sticky-header {
position: sticky;
top: 0; /* 뷰포트 기준 top으로부터 0px에 고정 */
}
.scrollable-content {
/* overflow는 여기만 */
}
z-index는 position이 static이 아닐 때만 작동한다. static인 요소에 아무리 z-index를 줘도 효과가 없다. 또한 부모의 z-index가 낮으면 자식이 아무리 높은 z-index를 가져도 다른 부모의 요소 앞으로 나올 수 없다.
/* ✗ 잘못된 코드 */
.background {
position: static;
z-index: 999; /* 작동하지 않음 */
}
.overlay {
position: relative;
z-index: 1;
}
/* ✓ 올바른 코드 */
.background {
position: relative; /* position이 있어야 z-index 작동 */
z-index: 1;
}
.overlay {
position: relative;
z-index: 2;
}
/* 또는 더 간단하게 */
.overlay {
position: relative;
z-index: 10; /* 부모 z-index와 무관하게 상대적으로 위에 배치 */
}
CSS position은 페이지 레이아웃의 핵심이다. static은 기본값일 뿐이고, relative는 자신의 원래 위치 기준, absolute는 부모(relative가 있어야 함) 기준, fixed는 뷰포트 기준, sticky는 부모 범위 내에서 고정이라는 점을 확실히 기억하자. 이 글의 비교표와 실수 사례를 참고해 position 속성을 올바르게 적용하면, 레이아웃 오류로 인한 시행착오를 크게 줄일 수 있을 것이다.