ARIA 및 키보드

aria-hidden="true" 안에 초점 요소를 방치할 때 생기는 스크린리더 침묵과 inert 해결책

모달이나 숨김 메뉴 컨테이너에 aria-hidden='true'를 지정했을 때 내부 링크·버튼의 초점을 비활성화하지 않아 발생하는 보조공학 침묵 오류의 원인과 최신 HTML inert 속성을 활용한 해결 절차를 다룹니다.

작성자 서호영 (기획·개발·콘텐츠 검토) · 발행 · 수정 · 읽기 6분

aria-hidden 초점 요소 방치 vs inert 속성 비교aria-hidden="true" 안에 포커스 가능한 요소가 남아있으면 접근성 트리 침묵 현상이 발생합니다. HTML inert 속성을 활용할 수 있습니다.W3C ARIA 1.2 규격 & HTML5 inert 속성 비교aria-hidden 안 초점 요소 방치<div aria-hidden="true"><button>배경 링크/버튼</button> (초점 도달)FAIL (4.1.2)초점은 닿지만 보조공학에는 숨겨져 완전 침묵 발생HTML inert 속성 적용<div inert>키보드 초점 차단 + 접근성 트리 완전 제외PASS (AA)시각적·초점적·보조공학적 격리가 온전히 일치
자체 검증 도식: aria-hidden="true" 요소 내부에 초점 가능한 컨트롤이 남으면 포커스와 접근성 트리의 상태가 어긋날 수 있습니다. 지원 대상과 호환성 범위를 확인한 뒤 inert 또는 다른 포커스 관리 방식을 적용할 수 있습니다.

실무 검증 기록

마크업 및 접근성 트리 대조

본 기록은 공개 fixture와 재현 조건을 기준으로 정리한 기술 설명입니다. 실제 브라우저·스크린리더 조합의 동작은 별도 수동 검토가 필요합니다.

접근성 기술 검증 상세 내역
검증 상태공개 fixture 기반 재현 완료
검증 기준공개 fixture 및 재현 조건
검증 범위aria-hidden 안 초점 요소 방치 방지 및 inert 속성 검증 및 명시된 입력값에 한정
수동 확인실제 브라우저·키보드·스크린리더 환경에서 별도 확인 필요
fixture에서 확인한 기대 결과[실패 상태] 보조기술에는 숨겼지만 키보드 포커스는 진입 ➔ [수정 목표] inert 또는 DOM 구조 정리

검증에 사용된 실제 픽스처 / 재현 조건

aria-hidden=true 내부 focusable button
검증 범위 및 한계
  • 구형 Safari 15 이하 등 inert 미지원 레거시 환경을 위한 polyfill 고려
  • 모달 외부 영역 전체에 inert를 적용하는 래퍼 DOM 구조 설계

주장과 직결된 W3C 공식 규격

웹 접근성 개선 작업을 진행하는 프론트엔드 실무자들이 가장 빈번하게 저지르면서도 치명적인 악영향을 미치는 실수 중 하나가 바로 aria-hidden='true' 속성과 키보드 초점 요소(Focusable Elements) 간의 충돌입니다. 주로 모달 레이어 팝업, 오프캔버스 슬라이드 메뉴, 탭 패널을 구현할 때 화면에서 일시적으로 감춰진 요소를 보조공학으로부터 숨기기 위해 컨테이너에 무심코 aria-hidden='true'를 부여합니다. 그러나 해당 컨테이너 내부의 <a> 링크나 <button>, <input> 요소의 키보드 초점을 비활성화하지 않고 그대로 방치하면, 스크린리더 사용자는 화면 탐색 중 갑자기 아무런 음성 안내도 나오지 않는 '침묵의 허공'에 갇히게 됩니다. 이 글에서는 aria-hidden과 키보드 초점의 동작 원리를 기술적으로 분석하고, 최신 웹 표준 속성인 inert를 활용한 근본적인 해결책을 상세히 제시합니다.

접근성 트리와 브라우저 포커스 트리의 이원화된 동작 원리

웹 브라우저는 웹페이지를 화면에 표시하기 위해 DOM(Document Object Model) 트리를 구성할 뿐만 아니라, 시각장애인용 스크린리더와 같은 보조공학 기기에 정보를 전달하기 위한 별도의 '접근성 트리(Accessibility Tree)'를 실시간으로 구축합니다. 또한 키보드 사용자의 Tab 키 조작을 관장하는 '포커스 내비게이션 트리(Sequential Focus Navigation Order)'를 유지합니다.

여기서 핵심적인 문제는 W3C ARIA 명세에 정의된 aria-hidden 속성의 영향력 범위입니다. aria-hidden='true' 속성은 오직 '접근성 트리'에서만 해당 노드와 하위 자식 노드를 완전히 제거(Remove from Accessibility Tree)할 뿐, 브라우저의 포커스 트리나 마우스 클릭 이벤트에는 단 0.1%의 영향도 주지 않습니다. 즉, HTML DOM 상에서 요소는 여전히 살아있으며 키보드 Tab 키를 누르면 당연히 초점을 받아갈 수 있는 상태로 남습니다.

충돌이 발생했을 때 시각장애인이 겪는 실제 경험

이 상태에서 스크린리더(NVDA, JAWS, VoiceOver 등) 사용자가 Tab 키를 눌러 웹페이지를 탐색하면 다음과 같은 치명적인 파국이 일어납니다.

브라우저의 포커스는 aria-hidden='true' 컨테이너 내부의 버튼이나 링크로 정상 이동합니다. 하지만 스크린리더는 브라우저로부터 초점 이동 알림을 받았음에도 불구하고, 접근성 트리를 조회했을 때 해당 요소가 aria-hidden 규칙에 의해 아예 존재하지 않는 유령 노드로 처리되어 있기 때문에 어떤 정보도 읽어줄 수 없습니다. 결과적으로 스크린리더는 완전히 침묵(Silence)하거나 '알 수 없는 객체'라는 오류 메시지만 출력합니다. 사용자는 웹사이트가 멈췄거나 브라우저가 다운되었다고 판단하여 사이트를 이탈하게 됩니다.

과거의 안티패턴과 임시방편의 부작용

과거에는 이 문제를 해결하기 위해 숨겨진 컨테이너 내부의 모든 <a>, <button>, <input>, <select> 요소를 자바스크립트로 일일이 찾아내어 tabindex='-1'을 동적으로 주입하고, 다시 화면에 나타날 때 원래의 tabindex 값을 복원하는 복잡한 수작업을 수행했습니다.

그러나 이 방식은 DOM 구조가 깊고 복잡한 대규모 애플리케이션에서 다음과 같은 심각한 부작용을 낳았습니다.

  • 1. 상태 관리의 복잡도 급증: 동적으로 추가되거나 교체되는 자식 요소에 대해 tabindex 동기화가 누락되어 틈새 버그가 끊임없이 발생합니다.
  • 2. 원래의 양수/0 tabindex 손실: 원래 고유한 포커스 속성을 가지고 있던 요소들의 기본 상태가 왜곡되어 복원 시 포커스 순서가 꼬이는 사고가 일어납니다.
  • 3. 클릭 및 터치 이벤트 유출: tabindex='-1'을 주더라도 모바일 터치나 마우스 클릭, 드래그 이벤트는 여전히 차단되지 않아 백그라운드 클릭 오작동이 남습니다.

inert 속성을 통한 초점 및 접근성 트리 동시 격리

현대 웹 표준(HTML5 Living Standard)은 이 고질적인 문제를 단 한 줄로 완벽하게 해결하는 네이티브 HTML 속성인 inert를 공식 지원합니다. 현재 Chrome, Edge, Safari, Firefox 등 모든 주요 모던 브라우저에서 inert가 기본 지원됩니다.

특정 컨테이너에 inert 불리언 속성을 선언하면(예: <aside inert>), 브라우저는 마법처럼 다음 세 가지 작업을 단번에 수행합니다. 첫째, 해당 노드와 하위의 모든 요소를 접근성 트리에서 자동으로 배제합니다. 둘째, 하위의 모든 요소에서 키보드 포커스(Tab 이동)를 완전히 물리적으로 차단합니다. 셋째, 마우스 클릭, 호버, 텍스트 선택 등 모든 사용자 인터랙션을 완벽하게 비활성화합니다.

따라서 모달 레이어 팝업을 띄울 때 배경 본문 컨테이너에 <main inert>를 부여하면, 과거처럼 복잡한 포커스 트랩 자바스크립트를 작성하거나 aria-hidden을 남발하지 않고도 가장 안전하고 표준화된 접근성 격리를 손쉽게 달성할 수 있습니다.

Chromium DevTools와 axe-core로 검출하는 자동화 진단 파이프라인

웹 애플리케이션의 규모가 커지면 개발자가 모든 모달이나 슬라이드아웃 메뉴의 aria-hidden 및 초점 요소를 일일이 손으로 추적하기 어렵습니다. 따라서 브라우저 개발자 도구와 CI/CD 자동화 테스트를 병행해야 합니다.

Chrome DevTools의 Elements 탭에서 요소를 선택한 뒤 하단 Accessibility 패널을 열면 Accessibility Tree 상에서 해당 노드가 Ignored 상태인지 확인할 수 있습니다. 부모에 aria-hidden="true"가 적용된 경우 노드 트리에 빗금이 그어지며 계산된 이름(Calculated Name)이 비활성화됩니다.

그러나 Console 패널에서 document.activeElement를 조회하거나 Tab 키를 연속해서 눌러보면, 시각적 포커스 링은 해당 숨겨진 노드에 여전히 머무르는 것을 확인할 수 있습니다. axe-core 룰셋의 aria-hidden-focus 규칙을 활용하면 CI 파이프라인에서 이를 사전에 차단할 수 있습니다.

  • DevTools Elements ➔ Accessibility 패널에서 "Ignored nodes" 및 노드 계산명(Calculated Properties) 즉시 확인
  • Playwright + axe-core 자동화 테스트: await new AxeBuilder({ page }).withRules(["aria-hidden-focus"]).analyze() 실행으로 무결성 검증
  • 시각적 포커스는 존재하나 스크린리더 음성이 완전히 침묵하는 고립 상태를 빌드 단계에서 원천 차단

React 모달 렌더링 시 전역 inert 폴리필 및 포커스 관리 아키텍처

현대 단일 페이지 애플리케이션(SPA)에서는 모달이 열릴 때 최상위 루트 노드 외의 형제 요소들을 비활성화해야 합니다. 과거에는 querySelectorAll로 모든 형제 요소를 탐색해 aria-hidden="true"를 주입했으나, 내부 폼 요소의 키보드 초점을 차단하지 못해 충돌이 발생했습니다.

HTML5 명세의 표준 inert 속성은 이러한 문제를 근본적으로 해결합니다. inert는 해당 DOM 하위 트리의 모든 대화형 요소를 브라우저 포커스 순서에서 즉시 제외하고, 접근성 트리에서도 완전히 숨기며, 마우스 클릭이나 터치 이벤트까지 차단합니다.

  • 표준 inert 속성: 모달 오픈 시 배경 컨테이너에 inert="" 속성을 주입하여 초점 트리와 접근성 트리를 동시 격리
  • React useInertBackground 커스텀 훅: 모달 마운트/언마운트 라이프사이클에 맞추어 appRoot의 inert 속성을 선언적으로 동기화
  • 구형 브라우저 대응: WICG inert 폴리필(wicg-inert)을 로드하여 CSS pointer-events와 tabindex="-1"의 자동 전파 보장

흔한 마크업 안티패턴과 올바른 예방책

잘못된 마크업 패턴올바른 접근성 구현
모달 바깥 영역 전체에 aria-hidden='true'만 걸어두고 내부 링크들의 Tab 포커스를 방치하는 경우모달 오픈 시 배경 컨테이너에 inert 속성을 부여하거나 모달 내부로 초점을 가두는 포커스 트랩을 구현합니다.
모달을 닫은 후에도 배경 컨테이너의 inert나 aria-hidden 속성을 해제하지 않아 메인 본문 조작이 불가능해지는 경우대화상자 닫힘 이벤트 시점에 배경의 inert를 제거하고 오프너 버튼으로 초점을 되돌리는 라이프사이클을 구성합니다.

보조공학 낭독 및 브라우저 파싱 한계

  • 구형 Safari 15 이하 등 inert 미지원 레거시 환경을 위한 polyfill 또는 tabindex="-1" 순회 fallback 필요
  • 모달 외부 영역 전체에 inert를 적용할 수 있도록 body 바로 아래에 최상위 래퍼(예: #main)를 두는 DOM 구조가 필요합니다.

주요 공식 상세 규격

규격 검토 기준일 2026-10-08

작성 및 검토 책임 정보

  • 작성자: 서호영
  • 실제 수행 역할: 기획·개발·콘텐츠 검토
  • 최초 작성일:
  • 최종 수정일:
  • 검증 기준: 공개 fixture 및 재현 조건

네이티브 HTML, ARIA 명세, 브라우저 및 스크린리더 조합에 따라 실제 보조공학 동작은 차이가 발생할 수 있습니다. 오류를 발견하셨거나 검증 데이터에 보완이 필요한 경우 오류 제보 및 문의 또는 liebe2460@naver.com로 알려주시면 사실 확인 후 정정합니다.