사이드바 메뉴나 목차에 position: sticky 속성을 적용했는데 고정되지 않고 그냥 지나쳐 버리는 현상을 겪어보셨다면, 혼자가 아닙니다.

Why CSS position: sticky Stops Working (And How overflow on html/body Breaks It)

이 글에서는 제가 직접 겪었던 실제 사례를 바탕으로 다음 내용을 다룹니다.

  • 스티키(sticky) 포지셔닝이 실패하는 진짜 이유
  • 개발자 도구(DevTools)에서 올바르게 진단하는 방법
  • htmlbody의 overflow가 원인이었던 이유와 해결 방법

재현 가능한 단계별 분석과 함께 알아보겠습니다.


position: sticky에 대한 흔한 오해

CSS 코드가 다음과 같이 완벽해 보일 수 있습니다.

css
.category-toc {
  position: sticky;
  top: 100px;
}

그럼에도 불구하고 다음과 같은 문제가 계속 발생합니다.

  • position: sticky가 설정되어 있는데도 아무 일도 일어나지 않음
  • top가 정의되어 있음
  • 어떤 부모 요소에도 overflow: hidden가 없음 (그렇다고 생각함)

그럼에도 스티키가 작동하지 않는다면, 선언 자체는 문제없지만 구조적인 무언가가 뒤에서 이를 무효화하고 있을 확률이 높습니다.


가장 먼저 확인해야 할 것 (개발자 도구로 진단하기)

크롬(또는 엣지) 개발자 도구를 열고 콘솔(Console) 탭으로 이동한 뒤 아래 코드를 실행해 보세요.

js
const el = document.querySelector('.category-toc');

getComputedStyle(el).position
getComputedStyle(el).top
getComputedStyle(el).overflow

[!NOTE] 콘솔에 코드를 처음 붙여넣는 경우, 크롬에서 보안상 이유로 수동으로 “allow pasting”을 입력해야 할 수 있습니다.

정상적인 결과의 모습

txt
position: "sticky"
top: "100px"
overflow: "visible"

이 값들이 올바르게 나오다면 sticky 선언 자체에는 문제가 없습니다.

이 점을 확인했는데도 여전히 고정되지 않는다면, 원인은 다른 곳에 있습니다.

가장 흔한 근본 원인: html / body의 overflow

제 경우의 범인은 htmlbody의 overflow 설정이었습니다.

콘솔을 확인해보니 다음과 같은 결과가 나왔습니다.

js
{
  scrollingElement: 'html',
  htmlOverflow: 'hidden auto',
  bodyOverflow: 'auto'
}

이는 실질적으로 다음을 의미했습니다.

  • html: overflow-x: hidden; overflow-y: auto
  • body: overflow: auto

즉, 두 개의 독립된 스크롤 컨테이너가 동시에 활성화되어 있던 것입니다.

html/body의 overflow가 sticky를 깨뜨리는 이유

position: sticky는 다음 요소를 기준으로 위치를 계산합니다.

  • 가장 가까운 스크롤 조상 요소, 또는
  • 조상 요소가 없는 경우 뷰포트

하지만 다음과 같은 상황에서는:

  • html도 스크롤 가능하고
  • body도 스크롤 가능하며
  • 그 위에 래퍼(wrapper) 요소까지 얹혀 있을 때

…브라우저는 sticky 요소를 어떤 컨테이너에 고정해야 할지 안정적으로 판단하지 못합니다. 그 결과, 요소가 sticky 포지셔닝을 완전히 무시하는 것처럼 보이게 됩니다.

해결 방법: html이 스크롤을 담당하게 하고 body를 기본 흐름으로 돌려놓기

가장 확실한 해결책은 다음과 같습니다.

css
html {
  overflow-x: hidden;
  overflow-y: auto;
}

body {
  overflow: visible;
}

핵심 포인트

  • 수직 스크롤은 html에만 집중시키기
  • body에는 overflow: auto를 설정하지 않기
  • 가로 스크롤 방지를 위한 overflow-x: hidden 설정은 유지해도 무방함

이 변경 사항을 적용하자마자, 작동을 거부하던 sticky 요소가 즉시 정상적으로 작동하기 시작했습니다.


콘솔에서 라이브로 먼저 테스트해보기

실제 CSS 파일을 수정하기 전에, 콘솔에서 임시로 이 동작을 테스트해볼 수 있습니다.

js
document.documentElement.style.overflowY = 'auto';
document.documentElement.style.overflowX = 'hidden';
document.body.style.overflow = 'visible';

이 코드를 실행한 후 sticky가 작동하기 시작한다면, overflow가 원인임을 확실히 진단한 것입니다.

다음과 같은 일반적인 회귀(Regression) 주의하기

나중에 코드베이스에 다음과 같은 요소가 추가되면 스티키 포지셔닝이 다시 깨질 수 있습니다.

  • html { overflow: hidden } 상태를 영구적으로 남겨두는 모달 구현
  • 관련 없는 이유로 body { overflow: auto }를 설정하는 레이아웃 코드
  • 자체 overflow 제어 기능이 결합된 height: 100vh 래퍼

모달이 열려 있는 동안 스크롤을 막아야 한다면, overflow: auto를 기본 상태로 유지하고 모달이 활성화된 동안에만 hidden로 전환하는 것이 더 안전합니다.

css
html {
  overflow-y: auto;
  overflow-x: hidden;
}

.modal-open html {
  overflow: hidden;
}

요약: sticky가 작동하지 않을 때 확인할 사항

  • positiontop가 올바르게 보인다고 해서 안심하지 않기
  • 바로 위의 부모뿐만 아니라 htmlbodyoverflow 확인하기
  • 페이지에 활성화된 스크롤 컨테이너가 오직 하나뿐인지 확인하기

대부분의 경우 sticky 포지셔닝 문제는 sticky 자체를 잘못 작성해서라기보다는 페이지가 실제로 어디서 스크롤되고 있는지에 기인합니다.

이 글로 다른 분들도 불필요한 디버깅 시간을 아끼실 수 있기를 바랍니다.