최근 누군가 링크드인에 스킵 링크(skip link)가 대상 요소에 tabindex 속성이 없어서 자주 망가진다는 글을 올렸습니다. 저는 그 문제가 이미 과거의 일이라고 생각했기 때문에 그 글을 보고 무척 놀랐습니다. 그래서 직접 테스트해 보기로 했습니다.
원래 문제
그 글의 작성자는 키보드로 스킵 링크에서 Enter 키를 누르면 페이지가 대상 위치로 스크롤되지만 포커스는 링크에 그대로 남는다고 설명했습니다. Tab 키를 누르면 포커스가 대상으로 이동하지 않고 스킵 링크 다음에 있는 포커스 가능한 요소로 이동한다는 것입니다. 그는 이를 “유령 점프(phantom jump)”라고 부릅니다.
<a href="#content">Jump to content</a>
<nav>
<!-- A bunch of links -->
</nav>
<main id="content">
</main>
작성자는 main 요소가 인터랙티브 요소가 아니어서 포커스를 받을 수 없는 것이 문제라고 설명합니다. 그래서 그가 제안한 해결책은 tabindex 속성을 추가하는 것입니다.
<a href="#content">Jump to content</a>
<nav>
<!-- A bunch of links -->
</nav>
<main id="content" tabindex="-1">
</main>
작성자가 설명한 문제는 아주 오래전에는 실제로 존재했습니다. Sara Higley가 정리한 글에 따르면 파이어폭스가 2013년에 처음으로 이 문제를 해결했고, 인터넷 익스플로러가 뒤를 이었으며, 크롬은 2016년에 해결했습니다. 사파리에 대해서는 잘 모르지만, 이제는 기억나지 않을 만큼 오래전 일입니다.
해결책
브라우저가 구현한 해결책은 순차 포커스 내비게이션 시작점(sequential focus navigation starting point, SFNSP)이라는 기능입니다. Rob Dodson은 자신의 글 “포커스 관리의 골칫거리 없애기”에서 이 기능을 잘 정의했습니다.
‘순차 포커스 내비게이션 시작점’ 기능은 포커스된 영역이 없을 때 순차 포커스 내비게이션(Tab 또는 Shift-Tab)을 위해 포커스 가능한 요소를 찾기 시작할 위치를 정의합니다.
그전에는 시작점이 될 수 있는 경우가 두 가지뿐이었습니다.
- 다른 시작점이 없다면
document가 시작점이며, 가능하다면 현재 활성화된dialog가 시작점입니다. - 어떤 요소에 포커스가 있다면 그 요소도 시작점입니다.
SFNSP가 생기면서 이제 시작점이 더 많아졌습니다.
- 스킵 링크를 클릭하는 등 페이지 프래그먼트(page fragment)로 이동하면 시작점이 설정됩니다.
- 페이지에서 인터랙티브 여부와 관계없이 어떤 요소든 클릭하면 시작점이 설정됩니다.
- 시작점이었던 요소가 DOM에서 제거되면 그 부모 요소가 시작점이 됩니다.
이 기능은 몇 가지 근본적인 접근성(accessibility) 문제를 해결했기 때문에 필요한 추가 기능이었습니다. 앞서 말했듯이 모든 주요 브라우저에서 수년 전부터 사용할 수 있었고 잘 작동합니다. 그래도 실제 테스트로 이를 뒷받침하고 싶었습니다. 이 글에서는 페이지 프래그먼트로 이동하면 시작점이 설정되는 시나리오에만 집중하겠습니다.
테스트
테스트를 위해 간단한 페이지를 만들었습니다. 다음 세 가지 결과를 확인하는 것이 목표였습니다.
- 스킵 링크에서 Enter 키를 누른 다음 Tab 키를 누르면 키보드로 “Content” 버튼에 도달합니다.
- 스킵 링크에서 Enter 키를 누른 다음 Tab 키를 누르면 스크린 리더와 키보드를 사용해 “Content” 버튼에 도달합니다.
- 스킵 링크에서 Enter 키를 누른 다음 스크린 리더의 가상 커서로 다음 요소로 이동하면 “Content” 버튼이나 h2에 도달합니다.
<header>
<a href="#content">Skip</a>
<button>1</button>
<button>2</button>
<button>3</button>
<button>4</button>
<button>5</button>
</header>
<main id="content">
<h2>Main content</h2>
<button>Content</button>
</main>
테스트한 모든 브라우저에서 세 가지 시나리오를 모두 확인할 수 있었습니다.
- 키보드, 파이어폭스 157, macOS 26.3
- 키보드, 크롬 145, macOS 26.3
- 키보드, 사파리, macOS 26.3
- 키보드, Windows 11, 크롬 145
- 키보드, Windows 11, 엣지 145
- 키보드, Windows 11, 파이어폭스 147
- Tab 키 / 가상 커서, VoiceOver, macOS 26.3, 사파리
- Tab 키 / 가상 커서, JAWS 2026, Windows 11, 크롬 145
- Tab 키 / 가상 커서, NVDA 2025.3.3, Windows 11, 파이어폭스 147
- Tab 키 / 가상 커서, NVDA 2025.3.3, Windows 11, 크롬 145
- Tab 키 / 가상 커서, Narrator, Windows 11, 엣지/크롬 145
- TalkBack, Android 16, 크롬 145
- ZoomText 2026, 키보드, Windows 11, 크롬 145
- 확대, 사파리, macOS 26.3
참고로 현재 아이폰이 없어서 아이폰 테스트는 할 수 없었습니다. 아이폰이 생기는 대로 테스트를 추가하겠습니다.
예외 및 기타 문제
- Windows 11에서 화면 확대를 700%로 설정하고 키보드를 사용하면 시작점은 올바르게 설정되지만 확대 화면이 항상 대상을 정확하게 따라가지는 않습니다. 제 테스트에서는 충분히 괜찮았지만, 레이아웃에 따라 달라지는 것으로 보입니다.
엣지 케이스와 특이한 동작
- VoiceOver에서 가상 커서를 사용할 때 VO + Space로 링크를 활성화하지 않고 Enter 키를 누르면 포커스가 스킵 링크에 그대로 남습니다. Enter 키를 VO + Space 대신 사용할 때 온갖 이상한 동작이 나타나기 때문에, 이런 경우까지 고려해야 하는지 또는 고려할 수 있는지 잘 모르겠습니다.
스타일 지정
인터랙티브하지 않은 페이지 프래그먼트로 이동해도 대상 요소의 포커스 스타일은 나타나지 않습니다. 해당 요소에 포커스가 생긴 것이 아니라 시작점만 설정되기 때문입니다. 그래도 CSS로 대상 요소의 스타일을 지정할 방법은 있습니다.
:target {
outline: 2px solid #ccc;
}
결론
제가 놓친 부분이 없다면(알려주세요), 스킵 링크 대상에서 tabindex="-1"을 제거해도 안전하다고 생각합니다.
링크드인 글을 링크하지 않은 것은 작성자를 지목하고 싶지 않았기 때문입니다. 작성자가 틀렸다는 이야기가 아니라, 브라우저와 스크린 리더가 자주 업데이트된다는 사실에 관한 이야기입니다. 수년간 따라온 모범 사례가 지금도 적용되는지 가끔씩 다시 확인하는 일이 중요한 이유입니다. 몇 년 전에는 사실이었던 내용이 오늘날에는 더 이상 사실이 아닌 경우가 많습니다.
추신
Dan Burzo는 tabindex=-1을 main 요소에 지정하면 iOS에서 뒤로 가기 버튼이 망가질 수 있다는 점을 발견했습니다.
업데이트 2026년 3월 5일
업데이트 1. 다른 문제가 발생하는 사례를 여러분의 도움으로 찾았기 때문에 글 제목을 좀 더 완화했습니다.
업데이트 2. 더 현실적인 결과를 얻을 수 있도록 데모 페이지를 개선했습니다. 글 링크를 추가했습니다.
'Web Frontend Developer' 카테고리의 다른 글
| [번역] 원샷으로 브라우저 API 만들기 (0) | 2026.09.21 |
|---|---|
| [번역] 인터랙티브 요소란 무엇일까요? (0) | 2026.09.21 |
| [번역] 프로그레시브 이미지 렌더링의 현재와 잠재적 미래 (1) | 2026.08.09 |
| [번역] Fetch 스트림은 훌륭하지만, 업로드/다운로드 진행 상황 측정에는 적합하지 않습니다 (0) | 2026.08.09 |
| [번역] 컴포넌트가 WCAG를 준수할 수 있을까요? (0) | 2026.06.05 |