Sticky Nav 때문에 앵커 링크가 가려질 때, 인라인 CSS 몇 줄로 깔끔하게 해결하기
문서(블로그/위키/가이드)에서 각주 링크나 TOC(목차)를 클릭하면 #some-id로 이동하면서 해당 id를 가진 요소가 뷰포트 최상단에 딱 맞춰지죠.
그런데 상단에 position: sticky(또는 fixed)로 고정된 네비게이션 바가 있으면, 스크롤은 최상단으로 맞춰지는데 실제 콘텐츠는 nav 뒤에 숨어버리는 문제가 생깁니다.
프론트엔드에서는 흔한 이슈지만, 백엔드/풀스택 입장에서는 “왜 링크가 제대로 갔는데 내용이 안 보이지?” 같은 상황이 꽤 난감할 수 있어요. 여기서는 제가 가장 자주 쓰는 “문제 생기는 페이지에 인라인 CSS 몇 줄 추가” 방식(가장 간단하고 즉효)을 중심으로 정리해볼게요.
왜 이런 일이 생길까?
브라우저의 기본 동작은 이렇습니다.
- URL이
.../page#target형태로 바뀌면 - 브라우저는
id="target"요소를 찾아 - 그 요소의 시작점이 스크롤 컨테이너의 시작(보통 뷰포트 top)에 오도록 스크롤합니다.
하지만 sticky nav는 뷰포트 위에 겹쳐서 렌더링되므로, 결과적으로 요소가 nav 아래에 가려집니다.

가장 간단한 해결책: scroll-margin-top (요소 쪽에 여백을 준다)
앵커로 스크롤되어 “보여질” 요소에 scroll-margin-top을 주면, 브라우저가 해당 요소를 최상단이 아니라 “여백만큼 아래”에 위치시키려 합니다.
인라인 CSS로 최소 적용 (추천)
문제 있는 페이지의 <head>나 해당 페이지 템플릿에 아래처럼만 넣어도 해결되는 경우가 많습니다.
<style>
:root { --sticky-nav-h: 64px; } /* nav 실제 높이로 맞추기 */
[id] { scroll-margin-top: calc(var(--sticky-nav-h) + 12px); }
</style>
--sticky-nav-h: sticky nav 높이+ 12px: 살짝 숨 막히지 않게 주는 여유(취향)
[id]전체에 적용하면 “이 페이지에서 앵커로 이동되는 모든 대상”이 자동으로 보정됩니다. 페이지 전체에 광범위하게 적용되는 게 부담스럽다면, 아래처럼 대상 범위를 줄이면 돼요.
범위를 줄여서 더 안전하게
예: 문서 본문 영역에서만 적용
<style>
:root { --sticky-nav-h: 64px; }
.article [id] { scroll-margin-top: calc(var(--sticky-nav-h) + 12px); }
</style>
예: TOC가 주로 h2/h3로 이동한다면
<style>
:root { --sticky-nav-h: 64px; }
.article h2[id], .article h3[id] {
scroll-margin-top: calc(var(--sticky-nav-h) + 12px);
}
</style>
함께 쓰면 더 편한 옵션: scroll-padding-top (컨테이너 쪽 기본 패딩)
scroll-padding-top은 “스크롤 스냅/프래그먼트 이동” 등에서 스크롤 컨테이너가 상단 안전영역을 갖도록 힌트를 줍니다.
실무에서는 scroll-margin-top이 더 직관적이지만, 조합해두면 레이아웃에 따라 안정성이 올라가는 경우가 있어요.
<style>
:root { --sticky-nav-h: 64px; }
html { scroll-padding-top: calc(var(--sticky-nav-h) + 12px); }
</style>
- 페이지 단위로 빠르게 “전체 앵커 이동 보정”을 하고 싶을 때 유용합니다.
- 다만 “특정 요소만” 세밀하게 제어하려면
scroll-margin-top이 편합니다.
레거시/호환성용 고전 트릭: ::before로 가짜 오프셋 만들기
아주 예전부터 쓰던 방식입니다. 앵커 타겟 요소 앞에 보이지 않는 블록을 만들어서, 실제 위치가 nav 아래로 내려오게 합니다.
<style>
:root { --sticky-nav-h: 64px; }
.anchor-target::before {
content: "";
display: block;
height: calc(var(--sticky-nav-h) + 12px);
margin-top: calc(-1 * (var(--sticky-nav-h) + 12px));
visibility: hidden;
pointer-events: none;
}
</style>
사용 예시:
<h2 id="install" class="anchor-target">설치</h2>
- 장점: 오래된 방식이라 “원리상” 확실히 동작하는 편
- 단점: 클래스 부여가 필요하고, 요소 선택/구조를 조금 더 신경 써야 함
요즘은 가능하면 scroll-margin-top부터 시도하고, 특정 환경에서 꼬이면 ::before 트릭을 고려하는 흐름이 깔끔합니다.
JS로도 해결 가능하지만, “페이지 단위 인라인 CSS”가 이기는 순간들
물론 JavaScript로 scrollIntoView() 후에 window.scrollBy(0, -navHeight) 같은 식으로 보정할 수도 있어요.
하지만 이런 경우엔 CSS가 더 낫습니다.
- 문서/정적 콘텐츠 페이지(각주/TOC가 많은 페이지)
- 프레임워크/라우팅 레벨 수정 없이 “해당 페이지에서만” 빠르게 해결하고 싶을 때
- 유지보수자가 FE 전문이 아닐 가능성이 높을 때
인라인 <style> 몇 줄은 의존성도 없고, 디버깅도 쉽고, 롤백도 쉬워서 문서성 페이지에 특히 잘 맞습니다.
실무 체크리스트
-
sticky nav 높이를 정확히 잡기
-
반응형으로 높이가 바뀐다면, 브레이크포인트별로
--sticky-nav-h만 바꿔도 됩니다. -
적용 범위를 너무 넓히지 않기
-
[id]전역 적용이 편하지만, 페이지 구조에 따라 과할 수 있어.article [id]처럼 제한 추천 -
“약간의 여유(+8~16px)”를 주면 체감이 좋아짐
-
딱 nav 높이만큼만 주면 심리적으로 붙어 보이는 경우가 많아요.
결론: 제가 가장 자주 쓰는 한 방
문제 생기는 페이지에 아래만 인라인으로 넣고 끝냅니다.
<style>
:root { --sticky-nav-h: 64px; }
.article [id] { scroll-margin-top: calc(var(--sticky-nav-h) + 12px); }
</style>
이걸로 TOC/각주/딥링크까지 한 번에 해결되는 경우가 대부분이니, 링크가 nav 뒤로 숨는 문제를 해결하고자 하시는 개발자분들에게 도움이 되었으면 합니다.
함께 보면 좋은 관련 글
- 이미지 태그에 width와 height를 명시하는 이유와 효과
- 백엔드 엔지니어라도 최소 이 정도는 알아야한다 - 프론트엔드 JS 메서드 , 모듈 Best 5