주소는 맞는데 소제목이 보이지 않는 경우

긴 상품 안내에서 ‘배송 조건’을 누르면 화면이 아래로 이동하지만 정작 제목은 고정 헤더 뒤에 숨어 버릴 수 있습니다. 이동은 실행됐으나 독자는 어디에 도착했는지 알아보기 어렵습니다. 여기에 목차를 클릭한 뒤 키보드 초점이 여전히 맨 위에 남는 문제까지 섞이면, 화면 위치만 고치는 작업으로는 읽기 흐름이 완성되지 않습니다.

이 글은 문서 안의 목차와 특정 절을 가리키는 링크를 다룹니다. 페이지의 반복 메뉴를 건너뛰는 본문 바로가기와 영역 구분보다 범위가 작습니다. 필요한 것은 ‘어느 절로 가는 주소인가’, ‘헤더 아래에서 제목이 보이는가’, ‘다음 키보드 행동은 어디서 이어지는가’라는 세 관계입니다.

클릭 처리보다 먼저 실제 절의 주소를 만듭니다

목차는 href="#shipping"처럼 문서의 ID를 가리키는 링크로 만들 수 있습니다. 독자가 그 주소를 복사하거나 새 탭에서 열어도 같은 절을 찾을 수 있어야 합니다. 버튼에 스크롤 함수만 연결하면 클릭은 작동해도 공유할 절 주소가 남지 않을 수 있습니다. 단순한 위치 이동이라면 기본 링크부터 만드는 편이 기능이 적고 고칠 곳도 명확합니다.

<nav aria-label="이 글의 목차">
  <a href="#shipping">배송 조건</a>
  <a href="#returns">반품 안내</a>
</nav>

<section aria-labelledby="shipping">
  <h2 id="shipping" tabindex="-1">배송 조건</h2>
  <p>상품별 발송 조건과 적용 지역을 설명합니다.</p>
</section>

<section aria-labelledby="returns">
  <h2 id="returns" tabindex="-1">반품 안내</h2>
  <p>반품 접수 방법과 적용 조건을 설명합니다.</p>
</section>

ID는 화면에 보이는 제목을 그대로 매번 생성하기보다 유지 가능한 이름을 정합니다. ‘배송 조건’을 ‘배송 지역과 발송 일정’으로 고칠 때 ID까지 바꾸면 외부에 공유한 이전 절 주소가 끊길 수 있습니다. 같은 문서에서 ID가 중복되면 원하는 제목에 도착한다는 보장도 없어집니다. 본문의 절을 합칠 때에는 이전 주소를 어느 위치로 연결할지 같이 결정하세요.

헤더 높이와 제목 위 여유를 나눠 잡습니다

아래 CSS는 헤더 높이를 72px, 제목 위 여유를 16px로 가정한 예제입니다. scroll-margin-top을 대상 제목에 설정해 스크롤 정렬 시 위쪽 여유를 둡니다. 일반 문서 흐름의 실제 바깥 여백을 88px 늘리는 코드가 아니므로 제목 사이 간격을 만들기 위해 쓰는 margin-top과 구분합니다.

:root { --header-height: 72px; }
.article-body h2[id],
.article-body h3[id] {
  scroll-margin-top: calc(var(--header-height) + 16px);
}
.article-body :target {
  background: #fff4ce;
}
@media (max-width: 640px) {
  :root { --header-height: 96px; }
}

모바일의 96px도 설명용 값입니다. 로고 아래 메뉴가 두 줄로 내려가거나 안내 배너가 추가되면 헤더 높이는 달라집니다. 화면 폭만으로 높이가 결정되지 않는 구성이라면 실제 높이를 읽어 CSS 변수를 갱신하거나, 헤더 자체를 고정하지 않는 방식도 고려할 수 있습니다. 숫자를 크게 잡아 제목이 화면 중간까지 내려가게 만드는 것은 읽는 공간을 낭비할 수 있습니다.

페이지 전체가 아니라 내부 패널이 스크롤되는 화면에서는 어느 요소가 스크롤 컨테이너인지 먼저 봅니다. 컨테이너의 안쪽 정렬 여유를 다루는 scroll-padding-top과 대상의 여유를 다루는 속성을 함께 사용하면 효과가 겹칠 수 있습니다. 같은 문제를 두 군데에서 보정하고 있지 않은지 확인한 뒤 한 가지 기준으로 정리하세요.

스크롤 위치와 키보드 초점은 따로 확인합니다

제목이 보인다고 해서 그 제목으로 키보드 초점이 옮겨졌다는 뜻은 아닙니다. 기본 해시 링크의 초점 동작은 대상 요소와 브라우저에 영향을 받으므로, 목차를 Enter로 실행한 뒤 다음 Tab이 어디로 가는지 실제로 확인해야 합니다. 제목에 tabindex="-1"을 두면 일반 Tab 순서에 제목을 추가하지 않으면서 프로그램으로 초점을 줄 수 있습니다.

이미 스크롤 위치를 정한 구현에서 초점만 옮겨야 한다면 아래와 같이 분리할 수 있습니다. focus()의 preventScroll 옵션은 초점 이동에 따른 자동 스크롤을 막습니다. 이 코드는 전체 앵커 기능이 아니라 특정 제목으로 초점을 옮기는 한 단계입니다. 해시 갱신과 이동 기록을 포함한 완성된 이벤트 처리로 착각하지 않아야 합니다.

const heading = document.getElementById("shipping");
if (heading) {
  heading.focus({ preventScroll: true });
}

모든 스크롤에 초점 코드를 붙이는 것도 피합니다. 독자가 손가락으로 화면을 움직일 때마다 초점까지 바뀌면 입력하던 위치를 잃을 수 있습니다. 목차 실행처럼 사용자가 명시적으로 절을 선택한 상황과 단순한 스크롤 관찰을 구분하세요. 대화상자가 닫힌 뒤 돌아갈 곳을 정하는 초점 복귀 설계와 마찬가지로, 다음 행동의 문맥이 기준입니다.

목차 클릭과 공유 주소의 첫 진입은 조건이 다릅니다

목차 클릭은 이미지와 글꼴이 이미 로딩된 뒤 일어날 수 있지만, /guide/#shipping으로 처음 들어오면 위쪽 콘텐츠의 높이가 나중에 바뀔 수 있습니다. 제목이 잠깐 맞았다가 이미지가 뜨면서 밀린다면 앵커의 여백보다 레이아웃 변화부터 살펴봐야 합니다. 이미지 공간 예약은 이때 함께 필요한 작업입니다.

진입 경로쉽게 놓치는 문제확인할 관계
목차에서 선택제목은 보이지만 다음 Tab이 목차에서 이어짐목적지 표시와 초점 이동
해시 주소로 첫 방문위쪽 이미지가 뜬 뒤 제목 위치가 밀림로딩 전후 배치와 도착 위치
뒤로·앞으로 이동주소와 보이는 절이 서로 다름브라우저 기록과 현재 콘텐츠
확대된 화면헤더 줄 수 증가로 제목이 다시 가림실제 헤더 높이와 남은 읽기 영역

표는 검사가 끝났다는 결과가 아니라 서로 다른 실패 원인을 구분하기 위한 예시입니다. 사용자 화면에서 문제가 생긴 경로를 먼저 재현하면 CSS 수치만 계속 바꾸는 시행착오를 줄일 수 있습니다.

도착한 제목을 읽을 수 있어야 이동이 끝납니다

고정 헤더는 초점이 있는 요소를 완전히 가릴 수도 있습니다. W3C의 초점 가림 설명은 작성자가 만든 콘텐츠 때문에 키보드 초점 대상이 완전히 숨겨지지 않아야 한다는 최소 기준을 다룹니다. 제목과 첫 설명을 편하게 읽는 설계는 그 최소 조건을 넘어 남은 화면 공간까지 살펴보는 작업입니다.

부드러운 스크롤을 넣는다면 긴 이동을 반드시 애니메이션으로 보여줄 필요가 있는지도 판단합니다. 움직임 감소 환경을 고려하고, 사용자가 도중에 다른 절을 선택했을 때 이전 이동이 계속 덮어쓰지 않도록 합니다. 목차의 선택 표시, 주소의 해시, 실제 보이는 제목이 서로 일치하면 독자는 현재 위치를 설명받지 않아도 다음 문단을 읽을 수 있습니다.