10. 웹접근성
WCAG 2.2 의 인식·운용·이해 원칙 — 대체 텍스트, 색상 대비, 키보드 접근성, `inert`, `<dialog>` aria-modal 등 모던 패턴 포함.
1. 웹 접근성 교안
Info: 📜 기준 가이드라인
- 국제 — WCAG 2.2 (W3C, 2023.10 권고). 4대 원칙 POUR (Perceivable / Operable / Understandable / Robust) 와 새로 추가된 9개 기준 (Focus Not Obscured, Focus Appearance, Dragging Movements, Target Size 등).
- 한국 — KWCAG 2.2 (한국지능정보사회진흥원, 2022.12 개정). WCAG 2.1 을 기반으로 한국 환경에 맞춰 24개 검사 항목 정리.
본 교안은 인식 / 운용 / 이해 세 원칙을 중심으로 기본 패턴을 다룹니다 (네 번째 원칙 Robust 는 시맨틱 마크업·ARIA 역할이라 본 강의 다른 챕터로 분산).
1. 인식의 용이성 (Perceivability)
Info: 📜 WCAG 2.2 Principle 1: Perceivable 에 대응.
1. 대체 텍스트 (Alternative Text)
개념: 이미지나 비텍스트 콘텐츠를 볼 수 없는 사용자를 위해 텍스트 설명을 제공합니다.
1. 예제 코드
<img src="cat.jpg" alt="흰색 고양이가 햇빛 아래 낮잠을 자고 있습니다">
<img src="line.png" alt="">
<div style="background-image: url('logo.png')"
role="img"
aria-label="회사 로고">
</div>
<button aria-label="검색">
<img src="search.png" alt="">
</button>
설명:
- 1번: 의미있는 이미지는 내용을 설명하는 alt 제공
- 2번: 장식만을 위한 이미지는 alt를 비워둠
- 3번: CSS 배경 이미지도 role과 aria-label로 설명 가능
- 4번: 아이콘 버튼은 버튼의 기능을 aria-label로 설명
2. 색상 대비 (Color Contrast)
개념: 텍스트와 배경 사이에 충분한 명도 대비를 제공하여 저시력 사용자도 내용을 읽을 수 있게 합니다.
2. 기준
- 일반 텍스트: 최소 4.5:1 대비율
- 큰 텍스트 (18pt 이상): 최소 3:1 대비율
3. 예제 코드
<p style="color: #000; background: #fff">
읽기 쉬운 텍스트입니다
</p>
<p style="color: #999; background: #fff">
읽기 어려운 텍스트입니다
</p>
<p style="color: #0056b3; background: #fff">
충분한 대비가 있는 텍스트입니다
</p>
<h1 style="font-size: 24pt; color: #666; background: #fff">
큰 제목 텍스트
</h1>
설명:
- 1번: 검정과 흰색은 최고의 대비율 제공
- 2번: 연한 회색은 대비가 부족하여 접근성 기준 미달
- 3번: 진한 색상 사용으로 충분한 대비 확보
- 4번: 큰 텍스트는 3:1 대비율만 필요
3. 자막 및 수화 (Captions and Sign Language)
개념: 청각 장애인을 위해 오디오 콘텐츠에 자막을 제공합니다.
4. 예제 코드
<video controls>
<source src="video.mp4" type="video/mp4">
<track kind="captions"
src="captions-ko.vtt"
srclang="ko"
label="한국어">
<track kind="captions"
src="captions-en.vtt"
srclang="en"
label="English">
</video>
<audio controls aria-describedby="podcast-transcript">
<source src="podcast.mp3" type="audio/mpeg">
</audio>
<details id="podcast-transcript">
<summary>전체 대본 보기</summary>
<p>여기에 오디오의 전체 대본을 텍스트로 제공합니다…</p>
</details>
설명:
- 1번: video 태그에 controls 속성으로 재생 컨트롤 표시
- 2번: source로 비디오 파일과 MIME 타입 지정
- 3번: track으로 자막 파일 연결 (kind="captions")
- 4번: 여러 언어 자막 제공 가능
- 5번:
<audio>는 사양상<track>자막을 받지 않습니다. 오디오 콘텐츠의 자막/대본은 별도 transcript(텍스트 대본) 로 제공하고aria-describedby로 연결하는 것이 표준.
4. 콘텐츠 구조화 (Content Structure)
개념: 올바른 HTML 구조로 콘텐츠의 위계와 의미를 명확하게 전달합니다.
5. 예제 코드
<h1>웹 접근성 가이드</h1>
<h2>인식의 용이성</h2>
<h3>대체 텍스트</h3>
<p>이미지에 대한 설명입니다.</p>
<h3>색상 대비</h3>
<p>색상 대비에 대한 설명입니다.</p>
<h2>운용의 용이성</h2>
<ul>
<li>첫 번째 항목</li>
<li>두 번째 항목</li>
</ul>
<ol>
<li>로그인하기</li>
<li>상품 선택하기</li>
<li>결제하기</li>
</ol>
설명:
- 1번: 페이지당 하나의 h1으로 메인 제목 표시
- 2번: h1 다음에 h2로 하위 섹션 시작
- 3번: 제목 레벨을 순서대로 사용 (건너뛰지 않음)
- 4번: 같은 중요도의 내용은 같은 제목 레벨 사용
- 5번: 새로운 주요 섹션은 h2로 시작
- 6번: 순서가 없는 목록은 ul 사용
- 7번: 순서가 중요한 목록은 ol 사용
2. 운용의 용이성 (Operability)
Info: 📜 WCAG 2.2 Principle 2: Operable 에 대응.
5. 키보드 접근성 (Keyboard Accessibility)
개념: 마우스 없이 키보드만으로 모든 기능을 사용할 수 있어야 합니다.
6. 예제 코드
<button onclick="save()">저장</button>
<div onclick="save()">저장</div>
<div tabindex="0"
role="button"
onclick="save()"
onkeypress="if(event.key==='Enter') save()">
저장
</div>
<a href="page.html">다음 페이지</a>
<input type="text" placeholder="이름 입력">
<select>
<option>옵션 1</option>
<option>옵션 2</option>
</select>
<input tabindex="1" type="text">
<input tabindex="2" type="text">
<div tabindex="-1" id="msg">알림 메시지</div>
<div role="dialog" aria-modal="true">
<h2>알림</h2>
<p>내용입니다</p>
<button>확인</button>
</div>
설명:
- 1번: button은 Tab, Enter, Space 키로 자동 작동
- 2번: div는 기본적으로 포커스 불가능
- 3번: tabindex="0"으로 포커스 가능하게 만듦
- 4번: a 태그는 Tab으로 이동, Enter로 클릭
- 5번: 모든 input, select 등은 키보드 접근 가능
- 6번: tabindex 양수는 탭 순서를 복잡하게 만들어 피해야 함
- 7번: tabindex="-1"은 JS로만 포커스 가능
- 8번: 모달에서는 포커스가 모달 내부에만 머물러야 함
6. 충분한 시간 제공 (Enough Time)
개념: 사용자가 콘텐츠를 읽고 사용할 충분한 시간을 제공합니다.
7. 예제 코드
<div id="slide">
<img src="slide1.jpg" alt="첫 번째 슬라이드">
<button onclick="pause()">일시정지</button>
<button onclick="play()">재생</button>
</div>
<script>
// 3. 자동 슬라이드 변수
let timer;
let paused = false;
// 4. 슬라이드 자동 전환 함수
function autoSlide() {
// 5. 일시정지 상태가 아닐 때만 실행
if (!paused) {
// 슬라이드 전환 코드
}
// 6. 5초마다 실행 (충분한 시간 제공)
timer = setTimeout(autoSlide, 5000);
}
// 7. 일시정지 함수
function pause() {
paused = true;
clearTimeout(timer);
}
// 8. 재생 함수
function play() {
paused = false;
autoSlide();
}
</script>
<div id="warn" style="display:none">
<p>5분 후 자동 로그아웃됩니다.</p>
<button onclick="extend()">시간 연장</button>
</div>
<script>
// 12. 타임아웃 전 경고 표시
setTimeout(function() {
document.getElementById('warn').style.display = 'block';
}, 25 * 60 * 1000); // 25분 후 경고
</script>
설명:
- 1번: 자동 슬라이드를 포함한 div 영역
- 2번: 사용자가 슬라이드를 제어할 수 있는 버튼
- 3번: 타이머와 일시정지 상태를 저장하는 변수
- 4번: 슬라이드를 자동으로 전환하는 함수
- 5번: 일시정지 중이 아닐 때만 슬라이드 전환
- 6번: 5초 간격으로 충분한 읽기 시간 제공
- 7번: 사용자가 일시정지 버튼을 누르면 타이머 중지
- 8번: 재생 버튼으로 다시 자동 전환 시작
- 9번: 세션 만료 경고를 위한 숨겨진 div
- 10번: 남은 시간을 알려주는 메시지
- 11번: 세션을 연장할 수 있는 버튼
- 12번: 타임아웃 5분 전에 경고 표시
7. 발작 예방 (Seizure Prevention)
개념: 깜빡이거나 번쩍이는 콘텐츠로 인한 발작을 예방합니다.
8. 예제 코드
<div style="animation: blink 0.3s infinite">
깜빡임
</div>
<style>
/* 2. 초당 3회 이상 깜빡임은 위험 */
@keyframes blink {
0% { opacity: 1; }
50% { opacity: 0; }
100% { opacity: 1; }
}
</style>
<button onclick="stop()">애니메이션 중지</button>
<div id="anim">
움직이는 콘텐츠
</div>
<script>
// 4. 애니메이션 중지 함수
function stop() {
// 5. CSS 애니메이션 제거
document.getElementById('anim').style.animation = 'none';
}
</script>
<style>
/* 7. 기본 애니메이션 */
.box {
transition: transform 0.3s;
}
/* 8. 사용자가 애니메이션 줄이기를 선택한 경우 */
@media (prefers-reduced-motion: reduce) {
.box {
/* 9. 모든 애니메이션과 전환 효과 제거 */
transition: none;
animation: none;
}
}
</style>
설명:
- 1번: 0.3초마다 깜빡이는 것은 발작을 유발할 수 있음
- 2번: 초당 3회 이상의 깜빡임은 접근성 위반
- 3번: 사용자가 애니메이션을 멈출 수 있는 버튼 제공
- 4번: 버튼 클릭 시 애니메이션 중지하는 함수
- 5번: CSS의 animation 속성을 none으로 설정
- 6번: 미디어 쿼리로 사용자 선호도 확인
- 7번: 일반 사용자를 위한 기본 애니메이션
- 8번: 애니메이션 줄이기 설정을 한 사용자 감지
- 9번: 해당 사용자에게는 모든 움직임 제거
8. 네비게이션 (Navigation)
개념: 사용자가 콘텐츠를 쉽게 탐색할 수 있도록 명확한 네비게이션을 제공합니다.
9. 예제 코드
<a href="#main" class="skip">본문 바로가기</a>
<nav aria-label="주 메뉴">
<ul>
<li><a href="/">홈</a></li>
<li><a href="/about">소개</a></li>
<li><a href="/contact">연락처</a></li>
</ul>
</nav>
<nav aria-label="주 메뉴">
<ul>
<li><a href="/">홈</a></li>
<li><a href="/about" aria-current="page">소개</a></li>
<li><a href="/contact">연락처</a></li>
</ul>
</nav>
<nav aria-label="빵부스러기">
<ol>
<li><a href="/">홈</a></li>
<li><a href="/products">상품</a></li>
<li aria-current="page">노트북</li>
</ol>
</nav>
<main id="main">
<h1>페이지 제목</h1>
<p>본문 내용입니다.</p>
</main>
<nav aria-label="목차">
<ul>
<li><a href="#sec1">섹션 1</a></li>
<li><a href="#sec2">섹션 2</a></li>
<li><a href="#sec3">섹션 3</a></li>
</ul>
</nav>
<section id="sec1">
<h2>섹션 1</h2>
</section>
<section id="sec2">
<h2>섹션 2</h2>
</section>
설명:
- 1번: 페이지 최상단에 본문으로 바로 가는 링크 제공
- 2번: nav 태그와 aria-label로 네비게이션 영역 명확히 구분
- 3번: 메뉴는 ul, li로 구조화하여 개수 파악 용이
- 4번: 네비게이션 영역 정의
- 5번: aria-current="page"로 현재 페이지 표시
- 6번: 빵부스러기는 사용자의 현재 위치 표시
- 7번: 순서가 중요하므로 ol 사용
- 8번: 현재 위치는 링크가 아닌 텍스트로 표시
- 9번: main 태그로 주요 콘텐츠 영역 표시
- 10번: 페이지 내 섹션으로 이동하는 목차
- 11번: href="#id"로 같은 페이지 내 이동
- 12번: 각 섹션에 고유한 id 값 부여
3. 이해의 용이성 (Understandability)
Info: 📜 WCAG 2.2 Principle 3: Understandable 에 대응.
9. 읽기 쉬운 텍스트 (Readable Text)
개념: 명확하고 이해하기 쉬운 언어를 사용합니다.
10. 예제 코드
<html lang="ko">
<p>
안녕하세요.
<span lang="en">Hello</span>입니다.
</p>
<p>
<abbr title="하이퍼텍스트 마크업 언어">HTML</abbr>은
웹 페이지를 만드는 언어입니다.
</p>
<p>
<dfn title="웹 페이지를 모두가 사용할 수 있게 만드는 것">
웹 접근성
</dfn>은 중요합니다.
</p>
<ruby>
漢字
<rt>한자</rt>
</ruby>
설명:
- 1번: html 태그에 lang 속성으로 페이지 언어 지정
- 2번: 문장 안에 다른 언어가 섞여 있는 경우
- 3번: 기본 언어는 한국어로 설정됨
- 4번: 영어 부분에만 lang="en" 추가
- 5번: 약어는 의미를 모를 수 있음
- 6번: abbr 태그의 title로 전체 명칭 제공
- 7번: 어려운 용어에 설명 필요
- 8번: dfn 태그의 title로 용어 정의
- 9번: 한자나 어려운 글자에 읽는 법 표시
- 10번: 원래 글자 (한자)
- 11번: rt 태그로 읽는 방법 (한글) 표시
10. 예측 가능성 (Predictability)
개념: 웹 페이지가 예측 가능한 방식으로 작동해야 합니다.
11. 예제 코드
<input type="text"
onfocus="openPopup()">
<input type="text" id="inp">
<button onclick="openPopup()">도움말 보기</button>
<select onchange="location.href=this.value">
<option value="page1.html">페이지 1</option>
<option value="page2.html">페이지 2</option>
</select>
<select id="sel">
<option value="page1.html">페이지 1</option>
<option value="page2.html">페이지 2</option>
</select>
<button onclick="go()">이동</button>
<script>
// 6. 사용자가 버튼을 눌러야 이동
function go() {
const sel = document.getElementById('sel');
location.href = sel.value;
}
</script>
<a href="page.html"
target="_blank">
외부 사이트 (새 창)
</a>
<a href="page.html"
target="_blank"
aria-label="외부 사이트, 새 창에서 열림">
외부 사이트
</a>
설명:
- 1번: 포커스만으로 팝업이 열리면 예측 불가
- 2번: 별도 버튼으로 사용자가 의도적으로 실행
- 3번: select 변경만으로 페이지 이동은 혼란스러움
- 4번: select는 선택만 함
- 5번: 이동 버튼을 따로 제공하여 명확한 의도 표현
- 6번: 버튼 클릭 시에만 페이지 이동 실행
- 7번: 새 창으로 열릴 때 텍스트로 명시
- 8번: 괄호 안에 "새 창" 표시
- 9번: 또는 aria-label로 스크린리더 사용자에게 알림
11. 입력 지원 (Input Assistance)
개념: 사용자가 오류를 방지하고 수정할 수 있도록 도와줍니다.
12. 예제 코드
<form>
<label for="name">이름</label>
<input type="text" id="name" required>
<label for="email">
이메일
<span aria-label="필수">*</span>
</label>
<input type="email"
id="email"
placeholder="example@email.com"
required
aria-describedby="tip">
<small id="tip">이메일 형식으로 입력해주세요</small>
<div id="err"
role="alert"
style="display:none; color:red">
</div>
<button type="submit">제출</button>
</form>
<script>
// 8. 폼 제출 시 유효성 검사
document.querySelector('form').addEventListener('submit', function(e) {
// 9. 기본 제출 동작 막기
e.preventDefault();
const name = document.getElementById('name').value;
const email = document.getElementById('email').value;
const err = document.getElementById('err');
// 10. 이름이 비어있는지 확인
if (!name) {
// 11. 오류 메시지 표시
err.textContent = '이름을 입력해주세요';
err.style.display = 'block';
// 12. 해당 필드로 포커스 이동
document.getElementById('name').focus();
return;
}
// 13. 이메일 형식 확인
if (!email.includes('@')) {
err.textContent = '올바른 이메일 형식이 아닙니다';
err.style.display = 'block';
document.getElementById('email').focus();
return;
}
// 14. 모두 정상이면 제출
alert('제출 완료');
});
</script>
<label for="pw">비밀번호</label>
<input type="password"
id="pw"
aria-describedby="pwrule">
<ul id="pwrule">
<li>8자 이상</li>
<li>영문, 숫자 포함</li>
</ul>
설명:
- 1번: 폼 요소들을 form 태그로 감싸기
- 2번: label의 for와 input의 id를 동일하게 연결
- 3번: label 안에 필수 표시 포함
- 4번: 별표(*)로 필수 필드 표시, aria-label로 의미 전달
- 5번: placeholder로 입력 예시 표시
- 6번: aria-describedby로 연결된 설명 텍스트
- 7번: role="alert"로 오류 메시지 영역 지정
- 8번: submit 이벤트로 유효성 검사 실행
- 9번: preventDefault로 바로 제출되지 않게 함
- 10번: 입력값이 비어있는지 확인
- 11번: 오류 메시지를 사용자에게 표시
- 12번: 오류가 있는 필드로 포커스 이동
- 13번: 이메일에 @가 있는지 간단히 확인
- 14번: 모든 검사 통과 시 실제 제출 진행
- 15번: 비밀번호 필드
- 16번: 비밀번호 규칙을 미리 안내
12. 오류 식별 및 제안 (Error Identification)
개념: 오류를 명확하게 표시하고 수정 방법을 제안합니다.
13. 예제 코드
<form id="frm">
<div>
<label for="user">사용자명</label>
<input type="text"
id="user"
aria-invalid="false"
aria-describedby="user-err">
<span id="user-err"
role="alert"
style="display:none; color:red">
</span>
</div>
<div>
<label for="age">나이</label>
<input type="number"
id="age"
aria-invalid="false"
aria-describedby="age-err">
<span id="age-err"
role="alert"
style="display:none; color:red">
</span>
</div>
<button type="submit">제출</button>
</form>
<script>
// 4. 폼 제출 이벤트 처리
document.getElementById('frm').addEventListener('submit', function(e) {
// 5. 기본 제출 막기
e.preventDefault();
// 6. 오류 여부 추적
let hasError = false;
// 7. 사용자명 검증
const user = document.getElementById('user');
const userErr = document.getElementById('user-err');
// 8. 빈 값 체크
if (!user.value.trim()) {
// 9. aria-invalid를 true로 변경
user.setAttribute('aria-invalid', 'true');
// 10. 오류 메시지 표시
userErr.textContent = '사용자명을 입력해주세요';
userErr.style.display = 'block';
hasError = true;
} else {
// 11. 정상이면 aria-invalid를 false로
user.setAttribute('aria-invalid', 'false');
userErr.style.display = 'none';
}
// 12. 나이 검증
const age = document.getElementById('age');
const ageErr = document.getElementById('age-err');
// 13. 나이 범위 체크
if (age.value < 1 || age.value > 120) {
age.setAttribute('aria-invalid', 'true');
// 14. 구체적인 수정 방법 제시
ageErr.textContent = '나이는 1~120 사이로 입력해주세요';
ageErr.style.display = 'block';
hasError = true;
} else {
age.setAttribute('aria-invalid', 'false');
ageErr.style.display = 'none';
}
// 15. 오류가 없으면 제출
if (!hasError) {
alert('제출 완료');
} else {
// 16. 오류가 있으면 첫 오류 필드로 포커스
if (user.getAttribute('aria-invalid') === 'true') {
user.focus();
} else {
age.focus();
}
}
});
</script>
설명:
- 1번: 각 필드에 고유한 id를 부여하여 구분
- 2번: aria-invalid로 현재 입력값의 오류 여부 표시
- 3번: role="alert"로 오류 메시지가 즉시 읽히도록 함
- 4번: 폼 제출 시 검증 로직 실행
- 5번: 검증 전에 제출되지 않도록 preventDefault
- 6번: 오류 발생 여부를 추적하는 변수
- 7번: 사용자명 필드와 오류 메시지 요소 가져오기
- 8번: 입력값이 비어있는지 확인 (공백 제거 후)
- 9번: 오류가 있으면 aria-invalid를 true로 설정
- 10번: 화면에 오류 메시지 표시
- 11번: 정상이면 aria-invalid를 false로 돌림
- 12번: 나이 필드 검증 시작
- 13번: 나이가 유효한 범위인지 확인
- 14번: 어떻게 수정해야 하는지 구체적으로 안내
- 15번: 모든 필드가 정상이면 제출 진행
- 16번: 오류가 있으면 첫 번째 오류 필드로 포커스 이동
4. 요약
웹 접근성의 세 가지 핵심 원칙:
-
인식의 용이성: 모든 사용자가 콘텐츠를 인식할 수 있어야 합니다
- 대체 텍스트 제공
- 충분한 색상 대비
- 자막 제공
- 명확한 구조
-
운용의 용이성: 모든 사용자가 인터페이스를 사용할 수 있어야 합니다
- 키보드 접근성
- 충분한 시간 제공
- 발작 예방
- 명확한 네비게이션
-
이해의 용이성: 콘텐츠와 인터페이스가 이해하기 쉬워야 합니다
- 읽기 쉬운 텍스트
- 예측 가능한 동작
- 입력 지원
- 오류 식별 및 제안
5. ▪ 2026 보완: WCAG 2.2 신규 + 모던 패턴
13. WCAG 2.2 신규 9개 기준 (2023.10 W3C 권고)
Info: 📜 WCAG 2.1 → 2.2 에서 추가 된 9개 기준. 기존 2.1 기준은 모두 유지되며 추가 된 항목들이다.
| 번호 | 기준 | 핵심 요구사항 |
|---|---|---|
| 2.4.11 | Focus Not Obscured (Min) | 포커스 받은 요소가 다른 콘텐츠(고정 헤더·쿠키 배너) 에 완전히 가려져선 안 됨 |
| 2.4.12 | Focus Not Obscured (Enhanced) | 포커스 요소가 전혀 가려져선 안 됨 (강화판) |
| 2.4.13 | Focus Appearance | 포커스 표시는 충분한 크기·대비를 가져야 함 |
| 2.5.7 | Dragging Movements | 드래그로만 가능한 동작은 단일 포인터 대안 (탭·클릭) 도 제공 |
| 2.5.8 | Target Size (Min) — 24×24 CSS px | 클릭 가능 요소의 터치 영역 은 최소 24×24 CSS 픽셀 |
| 3.2.6 | Consistent Help | 도움말 링크·연락처 는 모든 페이지에서 같은 위치 에 |
| 3.3.7 | Redundant Entry | 같은 정보를 다시 입력하라고 강요하지 않음 (자동 채움 또는 선택) |
| 3.3.8 | Accessible Authentication (Min) | 인지 기능 테스트 (퍼즐·암호 외움) 가 유일한 인증 수단이면 안 됨 |
| 3.3.9 | Accessible Authentication (Enhanced) | 객체 인식·개인 콘텐츠 테스트도 유일 하면 안 됨 |
Target Size 예시 — 24×24 CSS px 확보
<button class="icon-btn">
<span aria-hidden="true">🗑</span>
<span class="sr-only">삭제</span>
</button>
.icon-btn {
min-width: 24px;
min-height: 24px;
padding: 8px;
}
.sr-only {
position: absolute;
width: 1px; height: 1px;
margin: -1px; padding: 0;
overflow: hidden; clip-path: inset(50%);
white-space: nowrap; border: 0;
}
Info: 💡 간격 예외 — 작은 아이콘이라도 주변에 24×24 만큼의 여백 이 있어 인접 타겟과 겹치지 않으면 통과. 모바일 권장은 44×44 (Apple HIG·Material).
14. inert 속성 — 모달 바깥 비활성화
Info: 👁️🗨️ 요소와 그 자손 전체 를 "사용자가 만질 수 없는 상태" 로 만든다. 포커스 차단 + 클릭 차단 + 보조 기술이 없는 것처럼 처리.
<main id="page" inert>
<h1>본문</h1>
<button>저장</button>
<a href="#">링크</a>
</main>
<dialog id="alert" open>
<p>정말 삭제하시겠습니까?</p>
<button>확인</button>
<button>취소</button>
</dialog>
자바스크립트로 모달을 열 때 main.inert = true / 닫을 때 false 만 토글하면 수동 포커스 트랩 코드가 사라진다.
Info: 💡
<dialog>의showModal()은 나머지 페이지를 자동으로 inert 처리 한다. 직접 만든 모달(role="dialog"div) 일 때inert가 진짜 빛난다.
const dialog = document.getElementById('alert');
const main = document.getElementById('page');
function openAlert() {
main.inert = true;
dialog.show();
}
function closeAlert() {
dialog.close();
main.inert = false;
}
| 비교 | inert | aria-hidden="true" | tabindex="-1" |
|---|---|---|---|
| 포커스 차단 | ✓ | ✗ | ✓ (자기 자신만) |
| 클릭 차단 | ✓ | ✗ | ✗ |
| 보조 기술 차단 | ✓ | ✓ | ✗ |
| 자손까지 적용 | ✓ | ✓ | ✗ |
15. <dialog> 와 aria-modal
본문 2.1 키보드 접근성 의 마지막 예제는 수동 모달(<div role="dialog" aria-modal="true">) 패턴이다. 모던 권장은 <dialog> 요소 + showModal() — 9 챕터 참고.
<dialog id="confirm">
<h2>알림</h2>
<p>저장하시겠습니까?</p>
<form method="dialog">
<button value="ok">확인</button>
<button value="cancel">취소</button>
</form>
</dialog>
showModal() 이 다음을 자동 처리:
- 페이지 나머지 영역 inert 처리
- 포커스를 dialog 내부 첫 포커스 가능 요소로 이동
- ESC 키로 닫기 (
cancel이벤트 발생) ::backdrop으로 배경 표시
<form method="dialog"> 의 버튼이 값을 가지고 dialog 를 닫는다 — 결과는 dialog.returnValue 로 회수.
document.getElementById('confirm').showModal();
document.getElementById('confirm').addEventListener('close', e => {
console.log('결과:', e.target.returnValue); // "ok" 또는 "cancel"
});