WCAG 성공 기준 · Level A
WCAG 3.2.1: 포커스 시
WCAG 3.2.1 On Focus는 어떤 사용자 인터페이스 구성 요소가 키보드 포커스를 받을 때, 예기치 않은 컨텍스트 변경을 일으켜서는 안 된다고 요구합니다. 이는 키보드 및 보조 기술 사용자들이 페이지를 효과적으로 탐색하는 것을 불가능하게 만들 수 있는 혼란스럽고 예측 불가능한 동작으로부터 보호합니다.
- Level A
- Wcag
- Wcag 2 2 a
- 이해할 만한
- 접근성
이 규칙의 의미
WCAG 성공 기준 3.2.1 On Focus (레벨 A)는 다음과 같이 규정한다. “어떤 구성 요소가 포커스를 받더라도, 그로 인해 맥락 변화가 시작되어서는 안 된다.” 쉽게 말해, Tab, Shift+Tab, 화살표 키 또는 다른 키보드 메커니즘으로 포커스를 인터랙티브 요소로 옮기는 행위만으로는, 페이지에서 극적이고 예기치 않은 일이 발생해서는 안 된다는 뜻이다.
맥락 변화는 WCAG에서, 사용자가 인지하지 못한 채 발생하면 사용자를 혼란스럽게 만들 수 있는, 페이지 콘텐츠의 큰 변화를 의미한다. 명세는 네 가지 구체적인 맥락 변화를 제시한다. 사용자 에이전트의 변화(새 브라우저 창이나 탭을 여는 것 등), 뷰포트의 변화(페이지의 먼 위치로 자동 스크롤되는 것 등), 포커스 자체의 변화(포커스를 자동으로 다른 곳으로 옮기는 것 등), 그리고 페이지의 의미를 크게 바꾸는 콘텐츠 변화(폼 제출이나 완전히 다른 뷰를 로딩하는 것 등)이다.
이 기준이 강조하는 핵심 구분은 포커스를 받는 것과 컨트롤을 활성화하는 것 사이의 차이다. 버튼에 Tab으로 포커스를 옮기는 것만으로 폼이 제출된다면 이는 위반이다. 하지만 그 버튼에 포커스가 있는 상태에서 Enter나 Space를 눌러 의도적으로, 의식적으로 활성화하는 것은 완전히 허용되며 오히려 기대되는 동작이다. 사용자의 의도가 예측 가능한 상호작용과 혼란을 주는 상호작용을 가르는 기준이다.
이 기준을 위반하는 일반적인 패턴은 다음과 같다.
- 사용자가 선택을 확정하기도 전에, 어떤 옵션이 포커스를 받는 즉시 자동으로 새 URL로 이동하는
<select>드롭다운. - 어떤 입력 필드든 포커스를 받는 순간, 사용자 활성화 없이 모달 다이얼로그를 여는 날짜 선택 위젯.
- 내비게이션 점이 포커스를 받으면 자동으로 다음 슬라이드로 넘어가는 캐러셀 또는 슬라이드쇼.
- 포커스로 트리거될 때, 예고 없이 키보드 포커스를 자신에게로 동시에 옮겨 사용자를 예상치 못한 위치에 가두어 버리는 툴팁 또는 팝오버.
- 포커스를 받는 즉시 페이지를 제출하고 다시 로딩하는 검색 필드.
명시적으로 위반이 아닌 패턴에는 다음이 포함된다. 포커스를 옮기거나 페이지의 주요 콘텐츠를 바꾸지 않고 시각적으로만 나타나는 툴팁이나 설명 패널, 포커스된 요소 주위에 나타나는 포커스 인디케이터(윤곽선이나 링 등), 그리고 사용자가 둔 포커스가 그대로 유지되는 한, 인라인으로 추가 콘텐츠를 펼쳐 보여 주는 요소 등이다.
WCAG 3.2.1에는 공식적인 예외가 정의되어 있지 않다. 이 기준은 페이지의 모든 UI 구성 요소에 보편적으로 적용된다. 다만 WCAG Understanding 문서는, 사용자의 의도적인 활성화(클릭, Enter, Space)에 의해 트리거되는 맥락 변화는 이 기준의 범위 밖이라고 명시한다. 이 기준은 오직 수동적인 “포커스를 받는 행위”에만 초점을 맞춘다.
왜 중요한가
On Focus 기준은 원칙 3 — 이해 가능(Understandable) — 안에 위치하는데, 예측 가능성이 사용성의 근본적인 전제이기 때문이다. 페이지가 포커스만으로 예기치 않게 동작하면, 그 결과는 사용자의 필요와 도구에 따라 가벼운 혼란에서 완전한 접근 불가에 이르기까지 다양하다.
키보드만 사용하는 사용자(운동 장애, 반복성 긴장 장애, 마비 등으로 마우스를 사용할 수 없는 사람)는 오직 키보드 내비게이션에 의존한다. 폼 필드에 Tab으로 들어가는 것만으로 페이지가 다시 로딩되면, 이미 입력한 모든 데이터를 잃고 목표와는 다른 곳으로 이동할 수 있다. 이런 방해에서 회복하는 데는 상당한 시간과 노력이 들 수 있으며, 아예 포기해 버릴 수도 있다.
스크린 리더 사용자(대개 키보드만 사용하는 사용자이기도 하다)는 추가적인 혼란을 겪는다. 스크린 리더는 현재 포커스된 요소를 사용자에게 읽어 준다. 포커스가 예기치 않게 새 요소로 이동하면 — 예를 들어 자동으로 열린 모달로 — 스크린 리더는 방금 무슨 일이 왜 일어났는지에 대한 맥락 없이 새로운 상황만을 알려 준다. 이는 예고 없이 물리적으로 다른 방으로 옮겨지는 것과 비슷하다.
인지 장애가 있는 사용자(ADHD, 불안 장애, 기억 장애 등을 포함)는 예측 가능한 인터페이스에 의존해 페이지의 정신적 모델을 구축하고 유지한다. 갑작스럽고 설명되지 않은 맥락 변화는 그 모델을 깨뜨려 혼란, 불안, 오류를 유발한다. WebAIM Million 프로젝트의 연구는, 예측 불가능한 동작을 가진 복잡한 인터랙티브 구성 요소가 인지 장애가 있는 사용자에게서 제기되는 접근성 불만의 주요 원인 중 하나라는 점을 일관되게 보여 준다.
저시력 사용자 중 ZoomText나 Windows Magnifier 같은 화면 확대 소프트웨어를 사용하는 사람은 한 번에 화면의 작은 부분만 볼 수 있다. 포커스로 인해 자동 스크롤이나 내비게이션이 발생하면, 관련 콘텐츠가 확대된 뷰포트 밖으로 완전히 사라져 버려, 화면의 빈 공간이나 관련 없는 영역만 보게 될 수 있다.
구체적인 실제 사례를 생각해 보자. 한 터키 은행의 온라인 송금 폼에는 목적지 은행을 선택하는 드롭다운 메뉴가 있다. 개발자는 <select> 요소에, 선택 확정 시가 아니라 어떤 옵션이 화살표 키 포커스를 받는 즉시 실행되는 onchange 스타일 이벤트를 구현했다. 스크린 리더 사용자가 필드에 Tab으로 들어가서, 사용 가능한 은행을 탐색하기 위해 아래쪽 화살표를 누르는 순간, 폼 제출이나 페이지 리로드가 즉시 트리거된다. 사용자는 송금을 끝내지 못하고, 무엇이 잘못되었는지 알 수 없다. 이 시나리오는 가상의 것이 아니라, 초기 싱글 페이지 애플리케이션에서 실제로 문서화되었던 패턴이다.
접근성을 넘어, 사용성과 비즈니스 측면에서도 실질적인 이점이 있다. 포커스를 가로채지 않는 폼은 이탈률이 낮다. 예측 가능하게 동작하는 페이지는 모든 사용자 그룹을 대상으로 한 사용성 테스트에서 더 높은 점수를 받는다. 검색 엔진 크롤러 역시 예측 가능한 내비게이션 흐름의 이점을 누리는데, 포커스 이벤트로 트리거되는 예기치 않은 리다이렉트는 특정 동적 렌더링 시나리오에서 크롤링 로직을 혼란스럽게 만들 수 있기 때문이다.
관련 Axe-core 규칙
WCAG 3.2.1 On Focus는 자동화 도구가 사용자 의도를 신뢰할 수 있게 판단하거나 모든 가능한 맥락 변화를 예측할 수 없기 때문에 수동 테스트가 필요하다. Axe-core와 유사한 자동 스캐너는 정적 HTML과 ARIA 속성을 파싱할 수 있지만, 포커스 이벤트에 대한 런타임 JavaScript 동작 — 특히 그 동작이 WCAG가 정의하는 “중대한” 맥락 변화에 해당하는지 여부 — 를 관찰할 수 없다. focus 이벤트에서 포커스를 옮기거나, 폼을 제출하거나, URL로 이동하는 스크립트는, 도구가 실제로 모든 인터랙티브 요소에 대해 포커스 상호작용을 시뮬레이션하고, 그 후 DOM, 뷰포트, URL에서 무엇이 바뀌었는지 분석하지 않는 한, 정적 스캐너에는 보이지 않는다. 이런 수준의 동작 시뮬레이션은 허용할 수 없을 정도로 높은 오탐률 없이 자동화된 한 번의 검사로는 신뢰성 있게 달성하기 어렵다.
- 수동 테스트 필요 — 포커스 시 맥락 변화: 테스터는 페이지의 모든 인터랙티브 요소(링크, 버튼, 입력 필드, select, 커스텀 위젯)를 Tab으로 직접 이동해 보면서, 활성화 없이 포커스만으로 WCAG가 정의하는 맥락 변화가 발생하는지 관찰해야 한다. 여기에는 URL 변경, 새 창이나 탭 열림, 방금 Tab으로 이동한 요소에서 포커스가 다른 곳으로 옮겨지는지, 폼 제출, 주요 콘텐츠의 대규모 교체 등이 포함된다. 자동화 도구는
focus,focusin,onfocus같은 포커스 관련 이벤트에 연결된 JavaScript 이벤트 리스너를 수동 검토 후보로 표시할 수 있지만, 그 핸들러가 기준을 위반하는 맥락 변화를 일으키는지 여부는 판단할 수 없다.
테스트 방법
- 자동 사전 스캔: 페이지에 대해 axe DevTools(브라우저 확장 또는 CLI)나 Google Lighthouse를 실행한다. 두 도구 모두 On Focus 위반을 확정적으로 표시할 수는 없지만, axe DevTools는
scrollable-region-focusable이나 포커스 트랩 패턴처럼 포커스 관리와 관련된 문제를 드러낼 수 있으며, 이는 더 면밀한 수동 검사가 필요하다. axe DevTools의 “Needs Review” 패널을 활용하라. 그 안에 표시된 항목은 대개 사람의 판단이 필요한 인터랙티브 구성 요소 동작과 관련이 있다. - 모든 인터랙티브 요소 식별: 키보드 테스트 전에, 모든 인터랙티브 구성 요소 목록을 만든다. 링크, 버튼, 폼 입력, 드롭다운, 체크박스, 라디오 버튼, 날짜 선택기, 캐러셀, 아코디언, 탭, 모달, 그리고
tabindex를 사용하는 모든 커스텀 위젯을 포함한다. 특히focus나focusin이벤트를 수신하는 커스텀 JavaScript 위젯에 주의를 기울인다. - 키보드 전용 내비게이션 테스트: 마우스를 사용하지 않고 키보드만 사용해, 페이지의 모든 포커스 가능 요소를 Tab 키로 순차적으로 이동한다. 각 Tab 키를 누른 후, 다른 키를 누르기 전에 다음을 관찰한다. URL이 바뀌었는가? 새 창이나 탭이 열렸는가? 방금 Tab으로 이동한 요소에서 포커스가 다른 곳으로 옮겨졌는가? 폼이 제출되었는가? 페이지의 주요 콘텐츠가 극적으로 바뀌었는가? 하나라도 “예”라면 잠재적 위반이다.
- select 요소 테스트: 어떤
<select>드롭다운이든 포커스를 둔다. Enter나 Space를 누르지 않고, 위/아래 화살표 키로 옵션을 이동한다. 옵션을 이동하는 동안 내비게이션, 폼 제출, 맥락 변화가 발생하지 않는지 확인한다. 이는 가장 자주 위반되는 패턴 중 하나다. - NVDA + Firefox: NVDA(무료, Windows)를 활성화한다. Firefox를 열고 페이지로 이동한다. 모든 인터랙티브 요소를 Tab으로 이동한다. NVDA의 안내를 듣고, Enter나 Space를 누르지 않았는데도 Tab을 누른 후 NVDA가 페이지의 완전히 다른 부분이나 새로운 페이지 맥락을 안내하기 시작한다면, 이는 위반 가능성이 매우 높다.
- JAWS + Chrome: JAWS를 활성화한다. Chrome을 연다. Tab으로 내비게이션한다. JAWS는 포커스된 각 요소를 안내한다. 사용자가 의도적으로 이동하지 않았는데도 새 다이얼로그, 새 페이지, 예상치 못한 포커스 위치를 안내하는지 모니터링한다.
- VoiceOver + Safari (macOS/iOS): VoiceOver를 활성화한다(macOS에서는 Cmd+F5). Tab(또는 iOS에서는 스와이프)으로 내비게이션한다. 예기치 않은 맥락 변화를 모니터링한다. iOS에서는 스위치 액세스를 함께 테스트해, 스캔 방식으로 내비게이션하는 중증 운동 장애 사용자를 시뮬레이션한다.
- 브라우저 DevTools 이벤트 리스너 검사: Chrome DevTools에서 의심스러운 인터랙티브 요소를 선택하고, Elements 패널에서 “Event Listeners”를 클릭한다.
focus나focusin리스너가 있는지 확인한다. 있다면, 연결된 JavaScript를 검토해 핸들러가 내비게이션, 폼 제출, 포커스 이동 또는 기타 맥락 변화를 일으키는지 확인한다.
해결 방법
자동 제출 select 드롭다운 — 잘못된 예
<!-- FAIL: Selecting an option via arrow key immediately navigates to a new URL -->
<label for='region'>Select Region</label>
<select id='region' onchange='window.location = this.value;'>
<option value='/istanbul'>Istanbul</option>
<option value='/ankara'>Ankara</option>
<option value='/izmir'>Izmir</option>
</select>
자동 제출 select 드롭다운 — 올바른 예
<!-- PASS: Navigation only occurs when the user explicitly activates the Go button -->
<label for='region'>Select Region</label>
<select id='region'>
<option value='/istanbul'>Istanbul</option>
<option value='/ankara'>Ankara</option>
<option value='/izmir'>Izmir</option>
</select>
<button type='button' onclick='navigateToRegion()'>Go</button>
<script>
function navigateToRegion() {
var select = document.getElementById('region');
window.location = select.value; // Only fires on deliberate button activation
}
</script>
입력 포커스 시 모달 열기 — 잘못된 예
<!-- FAIL: Focusing the date input immediately opens a modal dialog and moves focus -->
<label for='departure'>Departure Date</label>
<input type='text' id='departure' onfocus='openDatePickerModal()' />
<script>
function openDatePickerModal() {
var modal = document.getElementById('date-modal');
modal.style.display = 'block';
modal.querySelector('button').focus(); // Moves focus away without user intent
}
</script>
입력 포커스 시 모달 열기 — 올바른 예
<!-- PASS: The date picker opens only when the user explicitly clicks or presses Enter/Space -->
<label for='departure'>Departure Date</label>
<input type='text' id='departure' readonly aria-haspopup='dialog'
aria-label='Departure Date — press Enter to open date picker' />
<button type='button' id='open-picker'
aria-controls='date-modal'
onclick='openDatePickerModal()'>
Choose Date
</button>
<script>
function openDatePickerModal() {
// Only called on explicit activation (click or Enter/Space on the button)
var modal = document.getElementById('date-modal');
modal.removeAttribute('hidden');
modal.querySelector('[data-initial-focus]').focus();
}
</script>
포커스 시 캐러셀 자동 이동 — 잘못된 예
<!-- FAIL: Focusing a navigation dot advances the carousel slide, changing page content -->
<div class='carousel-dots'>
<button class='dot' onfocus='showSlide(0)'>1</button>
<button class='dot' onfocus='showSlide(1)'>2</button>
<button class='dot' onfocus='showSlide(2)'>3</button>
</div>
포커스 시 캐러셀 자동 이동 — 올바른 예
<!-- PASS: The carousel only changes slides when the dot is explicitly activated (click/Enter) -->
<div class='carousel-dots' role='tablist' aria-label='Carousel navigation'>
<button class='dot' role='tab' aria-selected='true'
aria-controls='slide-0' onclick='showSlide(0)'>
Slide 1
</button>
<button class='dot' role='tab' aria-selected='false'
aria-controls='slide-1' onclick='showSlide(1)'>
Slide 2
</button>
<button class='dot' role='tab' aria-selected='false'
aria-controls='slide-2' onclick='showSlide(2)'>
Slide 3
</button>
</div>
<!-- onclick only fires on deliberate activation, not on Tab focus -->
자주 발생하는 실수
- 내비게이션 요소에서
onclick대신onfocus사용: 개발자가 내비게이션 링크나 버튼에 목적지를 “미리 로드”하기 위해onfocus핸들러를 붙였다가, 프리페치가 아니라 전체 내비게이션을 의도치 않게 트리거하는 경우가 있다. 맥락을 바꾸는 모든 동작에는 항상onclick또는 Enter/Space를 검사하는onkeydown을 사용해야 한다. - 제출 동작 없이
<select>요소에onchange바인딩: 데스크톱 브라우저에서는<select>의onchange가 옵션이 확정될 때 실행되지만, 일부 오래된 구현과 특정 모바일 브라우저에서는 화살표 키로 옵션을 이동하는 동안에도 실행될 수 있다. select 기반 내비게이션에는 항상 명시적인 제출 버튼을 함께 두거나,<button type='submit'>이 있는<form>을 사용해야 한다. focus이벤트 핸들러 안에서 프로그래밍 방식으로 포커스 이동: 어떤 요소의onfocus나focusin핸들러 안에서element.focus()를 호출하면 예기치 않은 포커스 점프가 발생한다. 사용자가 A 요소로 Tab을 눌렀는데, 포커스가 아무 설명 없이 B 요소로 옮겨지는 것이므로 직접적인 위반이다. 포커스 이동은 항상 사용자의 의도적인 행동에만 반응해 수행해야 한다.- 어떤 트리거 요소의 포커스 이벤트로 모달 다이얼로그 열기: 흔한 편법은 트리거 버튼이나 입력 필드의
focus이벤트에 모달 열기 핸들러를 붙이는 것이다. 모달은 클릭, Enter 키, Space 키에 대한 반응으로만 열려야 하며, 포커스만으로 열려서는 안 된다. - 포커스 시 뷰포트 맥락을 바꾸는 미디어나 애니메이션 자동 재생: 재생 버튼이 포커스를 받는 순간 전체 화면 비디오나 대형 애니메이션이 시작되는 히어로 배너는 시각적 맥락을 크게 바꾼다. 재생 동작은 포커스 이벤트가 아니라 활성화 이벤트에 연결해야 한다.
- 포커스 시 새 콘텐츠로 스크롤되는 라이브 리전 업데이트 트리거: 일부 동적 위젯은 입력이 포커스를 받는 즉시 라이브 리전을 업데이트하고, 그 리전으로 뷰포트를 스크롤한다. 이는 뷰포트 맥락을 바꾸고 화면 확대 사용자를 혼란스럽게 만든다. 가능하면 라이브 리전 업데이트를 포커스 이벤트와 분리하라.
- 사전 안내 없이 즉시 사용자를 가두는 커스텀 포커스 트랩 구현: 어떤 커스텀 구성 요소가 포커스를 받는 순간 모든 Tab 키 입력을 가로채면서, 사용자가 포커스 트랩 안에 있다는 사실과 탈출 방법을 알려 주지 않는다면, 이는 이 기준의 문구와 취지 모두를 위반한다. 포커스 트랩은 완전히 열린 모달 다이얼로그 내부에서만 적절하며, 사용자는 어떻게 나갈 수 있는지 안내받아야 한다.
- CSS
:focus로 포커스 가능한 자식을 포함한 드롭다운 메뉴에display: block적용, 연쇄적인 예기치 않은 포커스 이동 유발: CSS만으로 포커스 기반 메뉴를 열어 포커스 가능한 항목을 드러내면, 브라우저의 포커스 순서가 새로 보이는 항목으로 이동하면서 혼란스러운 점프가 발생할 수 있다. 드러나는 메뉴가 예상 가능한지, 그리고aria-expanded같은 ARIA 속성으로 명확히 전달되는지 확인하라. - 포커스 동작을 검토하지 않은 서드파티 위젯 라이브러리에 의존: 많은 UI 컴포넌트 라이브러리(날짜 선택기, 리치 텍스트 에디터, select2 스타일 드롭다운 등)는 역사적으로
focus이벤트에서 팝업을 열거나 포커스를 옮김으로써 3.2.1을 위반해 왔다. 라이브러리가 접근성을 주장하더라도, 배포 전에 항상 서드파티 구성 요소를 수동으로 검토해야 한다. - 싱글 페이지 애플리케이션(SPA) 라우팅 맥락에서 테스트를 잊는 것: React, Vue, Angular 기반 SPA에서는, 포커스 이벤트가 적절히 전파 중단되지 않았을 때, 내비게이션 링크의 포커스 이벤트가 라우터의 프리페치 로직이나 이벤트 버블링을 통해 라우트 변경을 트리거하는 경우가 있다. SPA 내비게이션 구성 요소는 3.2.1 준수 여부를 별도로 테스트해야 한다.
터키 접근성 규정과의 관계
터키의 2025/10번 대통령령(2025년 6월 21일 관보 제32933호 게재)은 WCAG 2.2를 기술 표준으로 명시적으로 참조하는 의무적 웹 접근성 요구 사항을 수립한다. WCAG 3.2.1 On Focus는 레벨 A 기준으로, 이는 대통령령 하에서 의무 준수의 최저선에 해당한다. 레벨 A 기준에는 예외가 없으며, 적용 대상 모든 기관은 정해진 기한 내에 이를 충족해야 한다.
공공 기관은 대통령령 공포일로부터 1년 이내에 완전한 적합성을 달성해야 한다. 범위 내 민간 부문 기관에는 2년이 주어진다. 대통령령 2025/10의 적용 대상에는 광범위한 조직이 포함된다. 모든 공공 기관 및 기관, 전자상거래 플랫폼과 온라인 마켓플레이스, 은행 및 금융 기관, 병원과 민간 의료 제공자, 200,000명 이상의 가입자를 보유한 통신사, 여행사와 예약 플랫폼, 민간 운송 회사, 그리고 교육부(MoNE)의 인가를 받은 민간 학교 및 교육 기관 등이 이에 해당한다.
WCAG 3.2.1 On Focus는 이러한 유형의 기관에 직접적이고 실질적인 관련성을 가진다. 예를 들어, 상품 카테고리 드롭다운이 포커스만으로 자동 내비게이션하는 전자상거래 플랫폼에서는, 이동 장애가 있는 키보드 사용자 고객이 상품 카테고리를 탐색하지 못하고 구매를 포기하게 된다. 포커스 트리거 제출이 있는 은행의 온라인 송금 폼은 스크린 리더 사용자에게 의도치 않은 금융 거래나 반복적인 실패를 초래할 수 있다. 날짜 필드가 포커스 시 모달을 여는 병원 예약 시스템은, 장애가 있는 환자가 스스로 진료를 예약하는 것을 막을 수 있다.
대통령령에 따라, 기준을 준수하지 않으면 적용 대상 기관은 행정 제재와 평판 리스크에 노출된다. 현재 디지털 전환을 진행 중이거나 새로운 웹 시스템을 도입하는 기관의 경우, 민원이 제기된 후 사후 보완하는 대신, 지금부터 조달 요구 사항과 개발자 가이드라인에 WCAG 3.2.1 준수를 포함시키는 것이 비용 효율적일 뿐 아니라 규정의 취지에도 더 부합한다. Accsible 오버레이 SDK를 사용하는 조직은, 더 넓은 WCAG 2.2 레벨 A 준수 워크플로의 일부로, 예기치 않은 포커스 기반 동작을 식별하고 수정하는 데 도움이 되는 내장 포커스 관리 도구의 이점을 누릴 수 있다.
출처 및 참고자료
- W3C Understanding 3.2.1 On Focus
- W3C Techniques for 3.2.1 On Focus
- WebAIM: Keyboard Accessibility
- MDN: HTMLElement: focus event
- MDN: tabindex attribute
- W3C G107: Using activate rather than focus as a trigger for changes of context
- W3C F55: Failure due to using script to remove focus when focus is received
