키보드 상호작용
최신 HTML inert 속성을 활용한 배경 비활성화와 모달 포커스 트랩의 완전한 종결
모달 팝업 오픈 시 뒷배경 DOM을 단 한 줄로 완벽히 격리하는 HTML5 inert 속성의 작동 원리와 접근성 트리 반영 메커니즘을 상세히 분석합니다.
실무 검증 기록
실무 판단 기준 및 판정 조건
본 기록은 공개 fixture와 재현 조건을 기준으로 정리한 기술 설명입니다. 실제 브라우저·스크린리더 조합의 동작은 별도 수동 검토가 필요합니다.
| 검증 상태 | 공개 fixture 기반 재현 완료 |
|---|---|
| 검증 기준 | 공개 fixture 및 재현 조건 |
| 검증 범위 | HTML Living Standard inert 속성 기반 모달 격리 검증 및 명시된 입력값에 한정 |
| 수동 확인 | 실제 브라우저·키보드·스크린리더 환경에서 별도 확인 필요 |
| fixture에서 확인한 기대 결과 | [실패 상태] 모달 뒤 배경 링크로 키보드 탭 유출 ➔ [수정 목표] main 태그에 inert 부여로 완벽한 하드웨어 레벨 격리 |
검증에 사용된 실제 픽스처 / 재현 조건
<main inert> + <dialog open aria-modal='true'>- inert 컨테이너 내부로의 직접 focus() 호출 시 무시되므로 모달 내부 이동 순서 정밀 제어 필요
- 구형 브라우저 대응을 위한 wicg-inert 폴리필 탑재 여부 수동 점검 필요
웹 애플리케이션에서 대화상자(Modal Dialog), 오프캔버스 사이드바 드로어, 풀스크린 내비게이션 메뉴를 구현할 때 프론트엔드 개발자들을 가장 괴롭혀온 난제는 '뒷배경 인터랙션의 완벽한 차단'이었습니다. 화면 중앙에 모달 팝업이 떠 있는 동안, 사용자의 키보드 초점과 마우스 클릭, 스크린리더의 가상 커서가 모달 창 밖으로 새어 나가 어두운 반투명 딤(Dim) 레이어 뒤편의 본문 링크나 헤더 메뉴를 건드리지 못하도록 가두는 포커스 트랩(Focus Trap) 작업은 악명 높은 노가다 작업이었습니다. 과거에는 이를 위해 수백 줄의 자바스크립트 이벤트 리스너를 붙이고, 모든 배경 요소에 aria-hidden='true'와 tabindex='-1'을 동적으로 주입하며 틈새 버그와 싸워야 했습니다. 그러나 최신 웹 표준(HTML5 Living Standard)에 공식 도입된 inert 속성은 이 고질적인 문제를 단 한 줄의 선언으로 완전히 종결지었습니다. 이 글에서는 inert 속성의 브라우저 내부 작동 메커니즘과 실무 모달 아키텍처 적용법을 깊이 있게 다룹니다.
과거의 포커스 트랩 구현 방식이 필연적으로 안고 있던 부작용들
inert 속성이 브라우저에 탑재되기 전, 프론트엔드 생태계가 모달 격리를 위해 사용했던 전통적인 기법들은 모두 심각한 기술적 부채를 수반했습니다.
첫 번째 방식은 'Keydown Tab 이벤트 가로채기'였습니다. 모달 내부의 첫 번째 포커스 요소와 마지막 포커스 요소를 쿼리 셀렉터로 찾아두고, 마지막 요소에서 Tab 키를 누르면 첫 번째 요소로, 첫 번째 요소에서 Shift+Tab을 누르면 마지막 요소로 강제 이동시키는 방식입니다. 이 방식은 키보드 Tab 키는 가둘 수 있지만, 마우스 휠 스크롤이나 마우스 클릭, 그리고 무엇보다 스크린리더가 지원하는 '가상 커서 방향키 탐색(Virtual Cursor Navigation)'은 전혀 차단하지 못해 시각장애인이 모달 뒤의 본문을 여전히 읽어버리는 치명적인 누수를 낳았습니다.
두 번째 방식은 '뒷배경 루트 노드에 aria-hidden과 tabindex 부여'였습니다. 그러나 이 방식은 모달이 닫힐 때 원래 각 요소가 가지고 있던 고유한 tabindex 값들을 완벽하게 기억하고 복원하지 못해 웹페이지의 포커스 흐름을 영구적으로 손상시키는 사고를 빈번히 유발했습니다.
HTML inert 속성이 단번에 수행하는 3대 브라우저 레벨 격리
특정 HTML 요소에 불리언 속성으로 inert를 선언하면(예: <div id='main-content' inert>), 브라우저 엔진은 자체 C++ 렌더링 파이프라인에서 다음 세 가지 조치를 일체형으로 즉시 단행합니다.
- 1. 접근성 트리에서의 완전한 소거: 해당 노드와 하위의 모든 자식 요소가 스크린리더를 위한 접근성 트리(Accessibility Tree)에서 보이지 않는 유령 노드로 처리되어 어떠한 음성 탐색도 불가능해집니다.
- 2. 키보드 초점 대상에서의 물리적 제외: 하위에 <button>, <input>, <a> 태그가 아무리 많이 들어있더라도 브라우저 포커스 내비게이션 트리에서 완전히 증발하여 Tab 키로 절대 접근할 수 없습니다.
- 3. 모든 사용자 인터랙션의 하드웨어 차단: 마우스 클릭, 마우스 호버(:hover), 텍스트 드래그 선택, 터치 제스처 등 모든 사용자 입력 이벤트가 해당 영역에서 완전히 무시됩니다.
React/Next.js 프로젝트에서의 실무 모달 컴포넌트 아키텍처
React 환경에서 inert 속성을 적용하는 가장 우아한 실무 패턴은 포털(React Portal)과 상태 관리를 연동하는 것입니다.
애플리케이션의 루트 레이아웃 구조를 모달 밖의 메인 영역(<div id='app-root'>)과 모달이 마운트될 컨테이너(<div id='modal-root'>)로 엄격히 양분합니다. 모달이 열리면(isModalOpen === true), React 상태에 따라 document.getElementById('app-root').setAttribute('inert', '')를 호출하거나 React 19의 <div id='app-root' inert={isModalOpen}> JSX 프로퍼티를 직접 바인딩합니다.
이 단 한 줄의 바인딩만으로, 모달이 떠 있는 동안 메인 웹페이지 전체가 완벽하게 불활성화(Inert State)됩니다. 모달이 닫히면 inert 속성이 제거되면서 이전의 모든 포커스 상태와 마우스 상호작용이 단 1ms의 오차도 없이 100% 원상 복구됩니다.
브라우저 지원 현황 및 레거시 폴리필(Polyfill) 전략
inert 속성은 현재 Chrome 102+, Safari 16+, Firefox 105+, Edge 등 전 세계 96% 이상의 모던 브라우저에서 기본 탑재되어 공식 지원되고 있습니다.
만약 공공기관이나 엔터프라이즈 환경에서 구형 브라우저 버전을 완벽히 지원해야 하는 요구조건이 있다면, W3C 웹 인큐베이터 커뮤니티 그룹(WICG)에서 공식 배포한 wicg-inert npm 패키지(Polyfill)를 빌드 파이프라인에 한 줄 임포트해 두면 됩니다. 구형 브라우저에서는 폴리필이 자동으로 동작하고 모던 브라우저에서는 네이티브 브라우저 엔진이 초고속으로 처리하므로, 성능과 접근성 양면에서 최상의 결실을 거둘 수 있습니다.
예외 조건 및 수동 검토 범위
- inert가 선언된 요소 내부에 포커스를 강제로 이동시키려고 focus()를 호출하면 무시되므로 모달 내부로의 초점 이동 순서를 주의해야 합니다.
- 시각적으로 비활성화되었음을 나타내기 위해 [inert] { opacity: 0.5; pointer-events: none; } CSS 스타일을 보조적으로 함께 지정하는 것이 좋습니다.