WCAG 성공 기준 · Level AAA
WCAG 2.4.12: 포커스 비가림 (강화됨)
WCAG 2.4.12는 UI 구성 요소가 키보드 포커스를 받을 때, 작성자가 만든 콘텐츠에 의해 해당 구성 요소의 어떤 부분도 가려지지 않아야 한다고 요구합니다. 즉, 포커스된 요소는 완전히 보여야 합니다. 이 강화된(AAA) 기준은 AA 기준에서 허용되던 부분 가시성을 없애, 키보드 사용자가 포커스가 어디에 있는지 항상 정확히 볼 수 있도록 합니다.
- Level AAA
- Wcag
- Wcag 2 2 aaa
- 작동 가능한
- 접근성
이 규칙의 의미
WCAG 2.4.12 — 포커스 비가림 (강화) — 은 WCAG 2.4.11(포커스 비가림, AA)의 AAA 대응 기준이다. AA 기준에서는 포커스를 받은 구성 요소가 부분적으로만 보여도 허용하지만, AAA 기준에서는 포커스를 받은 구성 요소가 완전히 보여야 하며, 키보드 포커스를 받을 때 작성자가 만든 어떤 콘텐츠에 의해서도 그 일부분조차 가려져서는 안 된다.
실질적으로 이는 사용자가 링크, 버튼, 폼 필드, 커스텀 위젯과 같은 인터랙티브 요소로 Tab 키를 이동할 때, 그 요소의 전체 경계 영역이 어떤 고정 헤더, 고정 푸터, 모달 오버레이, 쿠키 배너, 채팅 위젯 또는 작성자가 페이지에 배치한 다른 어떤 콘텐츠에 의해서도 가려지지 않아야 한다는 뜻이다. 이 규칙은 특히 작성자가 만든 콘텐츠를 대상으로 한다. W3C는 사용자가 직접 포커스 표시를 가리도록 이동시킨 콘텐츠 — 예를 들어 사용자가 포커스된 요소 앞에 끌어다 놓은 플로팅 패널 — 에 대해서는 명시적으로 예외를 둔다. 이 경우에는 작성자의 책임이 아니다.
2.4.12를 통과하려면, 포커스를 받았을 때 포커스를 받은 구성 요소 전체가 뷰포트 안에서 보여야 하며, 페이지 작성자가 제어하는 어떤 고정(sticky, fixed, absolute) 위치 요소에 의해서도 가려지지 않아야 한다. 실패는 포커스를 받은 요소의 보이는 경계 중 일부라도 이러한 오버레이 뒤에 숨겨질 때 발생한다. 포커스 링이나 구성 요소 자체의 단 1픽셀이라도 잘려 보이면 AAA 수준에서는 실패로 간주된다.
2.4.12가 다루지 않는 것이 무엇인지 이해하는 것이 중요하다. 이 기준은 특정한 포커스 표시 스타일을 요구하지 않는다(이는 2.4.11과 2.4.7에서 다룬다). 포커스 표시가 최소 대비 비율을 가져야 한다고 요구하지도 않는다(2.4.13에서 다룬다). 이 기준은 특히 페이지에서 포커스를 받은 요소와 다른 콘텐츠 사이의 공간적 관계 — CSS의 fixed 및 sticky 포지셔닝에서 가장 흔히 발생하는 레이어링 문제 — 를 다룬다.
영향을 받는 HTML 요소에는 포커스 가능 또는 Tab 이동이 가능한 모든 요소가 포함된다: <a>, <button>, <input>, <select>, <textarea>, <details>, tabindex가 있는 요소, 그리고 ARIA role로 만든 커스텀 인터랙티브 위젯 등이 있다. 이 기준은 iframes, 다이얼로그, 싱글 페이지 애플리케이션의 라우트 전환을 포함한 모든 브라우징 컨텍스트에 적용된다.
왜 중요한가
키보드 내비게이션은 매우 다양한 사용자에게 주요 접근 수단이다. ALS, 다발성 경화증, 뇌성 마비, 반복성 긴장 장애와 같은 질환을 가진 사람들을 포함한 운동 장애가 있는 사람들은 마우스 대신 키보드나 스위치 접근 장치에 전적으로 의존한다. 스크린 리더를 사용하는 시각장애 및 저시력 사용자도 키보드로 내비게이션하며, 보조 기술이 포커스 위치를 음성으로 알려주긴 하지만, 시력이 있는 키보드 사용자는 페이지에서 자신의 위치를 파악하기 위해 전적으로 시각적인 포커스 표시기에 의존한다.
포커스를 받은 요소가 부분적으로라도 가려지면, 이러한 사용자들은 짜증나고 잠재적으로 혼란스러운 경험을 하게 된다. 페이지에 포커스를 받은 요소가 전혀 없는 것처럼 보이거나, 문서에서 자신이 어디에 있는지 추측해야 할 수도 있다. AA 수준(2.4.11)에서는 부분적인 가시성이 허용된다. 구성 요소의 일부라도 보이면 단서가 되기 때문이다. AAA 기준은 이러한 타협을 완전히 제거한다. 대비 감도가 낮거나 터널 시야가 있거나, 화면을 스캔하는 것이 더 부담스러운 인지적 조건을 가진 사용자에게는 부분적으로 가려진 포커스 표시조차 놓치기 쉽다는 점을 인정하기 때문이다.
구체적인 상황을 생각해 보자. 한 터키 전자상거래 웹사이트가 뷰포트 상단에 높이 80px의 고정 내비게이션 바를, 하단에는 높이 60px의 고정 쿠키 동의 배너를 사용하고 있다. 사용자가 Tab 키를 눌러 상품 카드 사이를 이동할 때, 포커스를 받은 카드의 상단 또는 하단 모서리 — 포커스 링을 포함해 — 가 이 고정된 영역 중 하나 아래로 미끄러져 들어갈 수 있다. WCAG 2.4.11(AA)에서는 카드의 일부라도 여전히 보이면 사이트는 통과한다. 2.4.12(AAA)에서는 카드 전체가 완전히 보여야 한다. 이 차이는 의미가 크다. 부분적으로 가려진 버튼 레이블과 부분적으로 가려진 포커스 링이 결합되면, 저시력 사용자가 어떤 요소가 활성 상태인지, 어떤 동작을 수행할지 실제로 파악할 수 없게 만들 수 있다.
세계보건기구에 따르면 전 세계적으로 약 22억 명이 어떤 형태로든 시각 장애를 가지고 있으며, 운동 장애는 수억 명에게 영향을 미친다. 키보드 접근성 개선은 이들 집단뿐 아니라 속도를 위해 키보드 내비게이션을 선호하는 파워 유저, 포인팅 장치가 없는 기기 사용자, 미세 운동 조절이 일시적으로 저하된 상황에 있는 사용자에게도 도움이 된다.
장애 접근성을 넘어, 완전히 보이는 포커스는 전반적인 사용성을 향상시키고 지원 비용을 줄인다. 장애가 없는 사용자를 포함한 모든 사용자가 포커스 위치를 명확히 추적할 수 있을 때, 폼 완료율은 높아지고 오류율은 감소한다. 터키 시장을 대상으로 하는 사이트의 경우, AAA 준수 달성은 성숙한 접근성 프로그램을 보여주며 사용자와 기관 조달팀 모두에게 신뢰를 구축한다.
관련 Axe-core 규칙
WCAG 2.4.12는 수동 테스트가 필요한 항목으로 분류되며 WCAG 2.2 추가 항목의 일부이다. 이 위반을 신뢰성 있게 감지할 수 있는 완전 자동화된 axe-core 규칙은 없으며, 그 이유를 이해하는 것은 테스트 파이프라인을 구축하는 팀에게 중요하다.
- 수동 검사 — focus-not-obscured-enhanced (자동 규칙 없음): axe-core와 같은 자동 접근성 스캐너는 정적 DOM 또는 렌더링된 상태의 스냅샷에서 동작한다. 포커스를 받은 요소가 가려졌는지 감지하려면 (1) 모든 인터랙티브 요소에 대해 순서대로 키보드 포커스를 시뮬레이션하고, (2) 포커스로 인한 스크롤 이후 요소의 경계 사각형을 계산하며, (3) 모든 fixed 및 sticky 위치 요소와 그 경계 사각형을 식별하고, (4) 기하학적 겹침을 검사해야 한다. 부분적인 자동화는 이론적으로 가능하지만, 스크롤 동작의 동적 특성, CSS
scroll-padding, 부드러운 스크롤, JavaScript 기반 포커스 관리 때문에 실제로는 매우 신뢰성이 떨어진다. 한 뷰포트 크기에서는 포커스된 요소가 완전히 보이더라도 다른 크기에서는 완전히 가려질 수 있다. axe-core는 이 기준을 사람의 판단이 필요한 항목으로 표시하며, 결과를 자동 위반이 아닌 “검토 필요(needs review)”로 표시한다. 테스터는 모든 인터랙티브 요소를 Tab으로 직접 이동해 보며, 각 관련 뷰포트 너비에서 완전한 가시성을 눈으로 확인해야 한다. - scrollable-region-focusable (axe 규칙): 2.4.12에 직접 매핑되지는 않지만, 이 axe 규칙은 스크롤 가능한 영역 안에 있으면서 포커스 가능하지만 제대로 뷰로 스크롤되지 않을 수 있는 요소를 표시한다. 이는 sticky 헤더나 푸터에 의해 포커스가 가려질 수 있는 스크롤 관리 문제를 나타내는 관련 신호이며, 2.4.12의 가장 흔한 실패 유형이다.
자동 도구로는 2.4.12 위반을 신뢰성 있게 잡아낼 수 없기 때문에, 조직은 QA 프로세스에 수동 키보드 워크스루를 포함해야 하며, 이상적으로는 여러 뷰포트 크기에서, 그리고 모든 지속적인 UI 레이어(내비게이션 바, 채팅 위젯, 쿠키 배너, GDPR 알림)가 활성화된 상태에서 수행해야 한다.
테스트 방법
- 자동화된 기본 스캔: 페이지에 대해 axe DevTools 또는 Lighthouse를 실행하여
scrollable-region-focusable위반이나 CSS overflow 문제와 같은 관련 이슈를 식별한다. 이러한 결과는 직접적인 2.4.12 위반은 아니지만, 포커스 가림 문제를 일으킬 가능성이 가장 높은 페이지 영역을 나타낸다. axe DevTools에서는 WCAG 2.2 기준으로 필터링하고 포커스 가시성과 관련된 “검토 필요(needs review)” 항목을 확인한다. - 모든 지속적인 오버레이 콘텐츠 식별: 키보드 테스트 전에, 페이지에서
position: fixed또는position: sticky가 적용된 모든 요소 — 일반적으로 내비게이션 바, 쿠키 배너, 채팅 위젯, 플로팅 액션 버튼, 푸터 툴바 — 를 시각적으로 목록화한다. 이들이 차지하는 높이와 위치를 기록해 두어, 뷰포트의 어느 영역을 점유하는지 파악한다. - 키보드 내비게이션 워크스루: 페이지 상단(또는 초기 모달을 닫은 후)에서 시작해, Tab 키를 반복해서 눌러 모든 인터랙티브 요소로 포커스를 이동한다. 각 포커스 지점에서, 포커스를 받은 요소 전체 — 보이는 포커스 표시(아웃라인 또는 링)를 포함해 — 가 가려지지 않은 뷰포트 영역 안에 완전히 들어오는지 확인한다. 부분적인 가시성은 허용하지 않는다. 요소나 포커스 링의 일부라도 고정 요소 뒤로 사라지면 이를 2.4.12 실패로 기록한다.
- 역방향 내비게이션: Shift+Tab을 사용해 역방향으로 다시 워크스루를 수행한다. 고정 푸터는 순방향 테스트에서는 놓치기 쉽지만, 역방향 Tab 이동 시 요소를 가릴 수 있다.
- NVDA + Firefox로 스크린 리더 테스트: NVDA를 실행하고 Firefox를 열어 Tab으로 내비게이션한다. NVDA가 요소에 포커스를 알릴 때, 해당 요소가 완전히 보이는지 시각적으로 확인한다. NVDA의 포커스 모드는 고정 레이어를 자동으로 피하도록 스크롤하지 않기 때문에, 위반 사항이 브라우저 기본 동작과 다를 수 있다.
- VoiceOver + Safari(macOS/iOS)로 스크린 리더 테스트: VoiceOver를 활성화하고 Tab(또는 iOS에서는 스와이프)을 사용해 내비게이션한다. Safari의 스크롤 관리는 Chromium과 다를 때가 있어, Chrome에서는 보이지 않던 가려진 포커스 상태를 드러낼 수 있다.
- 반응형 뷰포트 테스트: 320px, 768px, 1024px, 1440px 너비 등 일반적인 브레이크포인트에서 키보드 워크스루를 반복한다. sticky 요소는 브레이크포인트에 따라 높이가 커지거나 위치가 바뀌는 경우가 많아, 위험 영역이 달라진다.
- 사용자 상호작용 이후 테스트: 드롭다운 메뉴를 열고, 아코디언을 확장하고, 모달을 띄우고, 싱글 페이지 애플리케이션에서 새로운 라우트로 이동한다. 각 상태 변경 후 Tab 내비게이션을 다시 시작해 포커스의 완전한 가시성을 재확인한다. 동적 콘텐츠는 종종 새로운 고정 오버레이를 도입하기 때문이다.
해결 방법
포커스된 링크를 가리는 스티키 헤더 — 잘못된 예
<!-- Fixed header with no scroll compensation -->
<header style='position:fixed; top:0; height:80px; background:#fff; width:100%;'>
<nav>...</nav>
</header>
<main>
<!-- When Tab reaches this link near the top of main, the header covers it -->
<a href='/products'>View all products</a>
</main>
포커스된 링크를 가리는 스티키 헤더 — 올바른 예
<!-- scroll-padding-top ensures focused elements scroll clear of the fixed header -->
<style>
html {
/* Match this value to the height of your fixed header */
scroll-padding-top: 88px; /* 80px header + 8px breathing room */
}
</style>
<header style='position:fixed; top:0; height:80px; background:#fff; width:100%;'>
<nav>...</nav>
</header>
<main style='margin-top:80px;'>
<!-- Focus now scrolls the element fully clear of the header -->
<a href='/products'>View all products</a>
</main>
뷰포트 하단 인터랙티브 요소를 가리는 쿠키 배너 — 잘못된 예
<!-- Cookie banner fixed to the bottom, no scroll compensation -->
<div id='cookie-banner' style='position:fixed; bottom:0; height:72px; width:100%; background:#222;'>
<button>Accept All</button>
<button>Manage Preferences</button>
</div>
<footer>
<!-- These links at the bottom of the page get covered by the cookie banner -->
<a href='/privacy'>Privacy Policy</a>
<a href='/terms'>Terms of Service</a>
</footer>
뷰포트 하단 인터랙티브 요소를 가리는 쿠키 배너 — 올바른 예
<!-- Add scroll-padding-bottom and body padding to compensate for the banner height -->
<style>
html {
scroll-padding-bottom: 80px; /* 72px banner + 8px breathing room */
}
body {
padding-bottom: 80px; /* Prevent content from being permanently under the banner */
}
</style>
<div id='cookie-banner' style='position:fixed; bottom:0; height:72px; width:100%; background:#222;'>
<button>Accept All</button>
<button>Manage Preferences</button>
</div>
<footer>
<!-- Links now scroll fully into the unobscured viewport area -->
<a href='/privacy'>Privacy Policy</a>
<a href='/terms'>Terms of Service</a>
</footer>
고정 레이어를 고려하지 않은 JavaScript 포커스 관리 — 잘못된 예
<!-- SPA route change: focus moved to heading but scrollIntoView ignores header -->
<script>
function navigateTo(section) {
const heading = document.querySelector('#' + section + ' h2');
heading.setAttribute('tabindex', '-1');
heading.focus();
// scrollIntoView with no offset — heading scrolls behind fixed header
heading.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
</script>
고정 레이어를 고려한 JavaScript 포커스 관리 — 올바른 예
<!-- Use scroll-margin-top on the target element, or manually offset scrollY -->
<style>
.focus-target {
/* scroll-margin-top offsets this element's scroll position from the top */
scroll-margin-top: 96px;
}
</style>
<script>
function navigateTo(section) {
const heading = document.querySelector('#' + section + ' h2');
heading.setAttribute('tabindex', '-1');
// scroll-margin-top on the element handles the visual offset automatically
heading.classList.add('focus-target');
heading.focus();
// scrollIntoView now respects scroll-margin-top, clearing the fixed header
heading.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
</script>
자주 발생하는 실수
body에scroll-padding-top을 설정하고html에는 설정하지 않는 경우: CSSscroll-padding속성은 스크롤 컨테이너에 적용해야 한다. 전체 페이지 스크롤의 경우 스크롤 컨테이너는body가 아니라html요소다. 대부분의 브라우저에서body에 적용하면 효과가 없으며, 가장 흔한 구현 오류 중 하나다.- 모든 브레이크포인트에서 실제 헤더 높이와 일치하지 않는 고정 픽셀 값으로
scroll-padding-top을 하드코딩하는 경우: 헤더가 모바일에서 더 작은 높이로 접히거나 데스크톱에서 보조 내비게이션 바를 포함하도록 확장되면, 정적인 오프셋 값은 잘못된다. JavaScript로 업데이트되는 CSS 커스텀 프로퍼티를 사용하거나, 상대 단위와 함께calc()를 사용해 값을 동기화 상태로 유지하라. - 페이지 내 앵커 타깃에
scroll-margin-top을 설정하지 않는 경우: 전역scroll-padding-top이 Tab 내비게이션에는 올바르게 설정되어 있더라도, 프로그래밍 방식으로 포커스를 받는 앵커 링크 타깃(예: 건너뛰기 링크, SPA의 해시 내비게이션)은 해당 요소에scroll-margin-top을 설정하지 않으면 여전히 헤더 아래에 위치할 수 있다. - 쿠키 배너를 닫은 후에만 테스트하는 경우: 많은 팀이 쿠키 배너를 수락한 후에만 키보드 내비게이션을 테스트한다. 배너는 뷰포트 하단을 차지하기 때문에, 하단에 고정된 포커스 가능 요소는 배너가 활성화된 동안에만 가려질 수 있다. 항상 모든 지속적인 UI 레이어가 완전히 표시된 상태에서 테스트해야 한다.
- 하나의 뷰포트 너비에서만 테스트하는 경우: sticky 요소는 브레이크포인트에 따라 높이가 바뀌거나, 보이게 되거나, 완전히 사라지기도 한다. 375px에서 발생하는 실패는 1440px에서는 나타나지 않을 수 있고 그 반대도 마찬가지다. 한 가지 크기에서만 테스트하면 실제 환경에서 발생하는 위반의 상당 부분을 놓치게 된다.
- 부모 컨테이너에
overflow: hidden을 사용해 포커스 표시를 잘라내는 경우: 카드 컴포넌트나 컨테이너에overflow: hidden이 설정되어 있으면, 자식 요소의 브라우저 기본 포커스 아웃라인이 컨테이너 경계에서 잘린다. 이로 인해 DevTools 요소 검사에서는 포커스가 완전히 보이는 것처럼 보이지만, 실제 사용자에게는 시각적으로 잘려 보일 수 있다. - 스크린 리더가 스크롤을 자동으로 처리한다고 가정해 시각적 테스트를 생략하는 경우: 스크린 리더는 포커스를 받은 요소를 음성으로 알려주지만, 화면 확대 도구를 사용하는 사람을 포함한 시력이 있는 키보드 사용자는 전적으로 시각적 위치에 의존한다. 시각적으로 가려진 포커스 상태는 스크린 리더 동작과 무관하게 실제 실패이다.
- 모달 다이얼로그와 드로어 오버레이를 테스트하지 않는 경우: 모달이 열리고 포커스가 그 안으로 이동할 때, 배경이나 모달 크롬 자체가 다이얼로그 내부에서 처음 포커스를 받는 요소를 가릴 수 있다. 이는 특히 측면이나 하단에서 애니메이션으로 들어오는 드로어 스타일 패널에서 흔하다.
- 라이브 채팅 버블, 전면 광고 배너와 같은 서드파티 위젯을 무시하는 경우: Intercom, Zendesk와 같은 플로팅 채팅 위젯과 태그 매니저가 삽입하는 고정 프로모션 배너는 작성자가 만든 콘텐츠에 해당하며 이 기준의 범위에 포함된다. 이들은 메인 코드베이스 밖에서 관리되기 때문에 팀이 종종 간과한다.
- 자동 접근성 스캔에만 의존하고 티켓을 종료하는 경우: 2.4.12는 수동 테스트가 필요하기 때문에, 깨끗한 axe-core 스캔 결과만으로는 준수를 확인할 수 없다. 자동 결과만을 근거로 접근성 티켓을 종료하면 이 기준을 지속적으로 놓치게 된다.
터키 접근성 규제와의 관계
터키의 대통령령 서한 2025/10은 2025년 6월 21일 관보 제32933호에 게재되었으며, 터키에서 운영되는 광범위한 주체에 대해 웹 및 모바일 접근성 요구사항을 법적으로 규정한다. 이 서한은 WCAG 2.1 AA 수준을 기본 준수 표준으로 채택하고 있으며, 이는 WCAG 2.4.12가 WCAG 2.2의 AAA 기준이기 때문에 현재 규제 하에서는 직접적으로 의무화되어 있지 않다는 뜻이다. 그러나 이 기준이 터키의 접근성 프레임워크와 맺는 관계는 여러 이유에서 중요하다.
대통령령 서한 2025/10의 적용 대상에는 모든 수준의 공공기관 및 정부 기관, 전자상거래 플랫폼, 은행 및 금융 서비스 제공자, 병원 및 의료 기관, 가입자 200,000명 이상인 통신사, 여행사, 민간 운송 회사, 그리고 교육부(MoNE)의 인가를 받은 사립학교가 포함된다. 이들 모든 조직에 대해 WCAG 2.1 AA 준수는 법적 의무이며, 서한은 관련 감독 기관이 관리하는 감사 메커니즘을 통해 집행될 것으로 예상된다.
서한에서 AAA 준수를 요구하지는 않지만, 규제 대상 부문에 있는 조직은 WCAG 2.4.12 준수를 추구할 강력한 실질적 이유가 있다. 첫째, 터키의 규제 환경은 진화하고 있다. 이 서한은 이전 지침에 비해 접근성 집행을 크게 강화한 것이며, 향후 개정에서 WCAG 2.2를 채택하거나 준수 수준을 상향할 수 있다. 지금 AAA 관행을 구축하는 조직은 규제 변화에 더 잘 대비할 수 있다. 둘째, 공공 조달 절차와 EU 시장 접근은 점점 더 강화된 접근성 프로그램을 입증할 수 있는 공급자를 선호하고 있으며, AAA 준수 문서는 경쟁력 있는 차별화 요소가 된다.
셋째, 그리고 WCAG 2.4.12와 가장 직접적으로 관련된 점은, 이 기준이 터키의 보조 기술 사용자 — 운동, 시각, 인지 장애를 모두 합산할 때 수백만 명으로 추정되는 인구 — 에게 불균형적으로 영향을 미치는 실패 유형을 다룬다는 것이다. 고정 내비게이션 크롬과 지속적인 알림 레이어에 크게 의존하는 은행, 병원, 전자정부 포털은 포커스 가림 실패가 가장 흔한 사이트 유형이다. WCAG 2.4.12를 완전히 준수하기 위한 투자는 모든 사용자를 위한 진정한 서비스 의지를 보여주며, 아직 법 문구에서 요구하지 않더라도 대통령령 서한의 취지와 부합하고, 터키의 집행이 성숙해짐에 따라 법적·평판적 위험을 줄여준다.
Accsible 오버레이 SDK를 사용하는 조직의 경우, 이 플랫폼은 키보드 포커스 경로를 감사하고 sticky 포지셔닝 충돌을 식별하는 도구를 제공하여, 대통령령 서한 2025/10의 의무적인 AA 요구사항과 WCAG 2.4.12와 같은 자발적인 AAA 강화 조치를 모두 지원한다.
