접근성 (a11y)
WCAG 2.2 및 Lighthouse 접근성 감사에 기반한 포괄적인 접근성 가이드라인입니다. 목표: 장애를 가진 사람을 포함한 모든 사람이 콘텐츠를 사용할 수 있도록 합니다.
증거 기반 감사 워크플로우
렌더링된 페이지가 있을 때:
- 해당 기능을 사용할 수 있을 때 실시간 Lighthouse 접근성 감사를 실행합니다. Chrome DevTools MCP에서는
lighthouse_audit을 사용합니다. 일반 공개 페이지에는 모바일 탐색 모드를, 리로드 시 인증 또는 사용자 생성 상태가 손실되는 경우에는 스냅샷 모드를 사용합니다. - 실패한 감사 노드를 사용하여 전체 리포지토리에서 일반적인 패턴을 검색하는 대신 관련 컴포넌트 또는 템플릿의 위치를 파악합니다.
- 이름, 역할, 상태, 랜드마크 및 제목 구조에 대해 렌더링된 접근성 트리 스냅샷을 검사합니다. Chrome DevTools MCP에서는
take_snapshot을 사용합니다. 키보드로 영향을 받는 흐름을 직접 테스트합니다. - 소스를 수정한 후 동일한 감사 및 수동 상호작용을 다시 실행합니다.
실시간 도구를 사용할 수 없는 경우, Lighthouse CLI 또는 axe를 사용하여 자동화된 적용 범위를 확보하고 동일한 수동 점검을 완료합니다. 자동화 도구는 접근성 장벽의 일부만 감지할 수 있습니다. 100점은 WCAG 준수를 의미하지 않으며, 낮은 점수가 문제 수준의 증거를 대체하지 않습니다.
WCAG 원칙: POUR
| 원칙 | 설명 |
|---|---|
| Perceivable (지각 가능) | 콘텐츠가 다른 감각을 통해 인지될 수 있음 |
| Operable (운용 가능) | 인터페이스가 모든 사용자에 의해 작동될 수 있음 |
| Understandable (이해 가능) | 콘텐츠와 인터페이스가 이해 가능해야 함 |
| Robust (견고함) | 콘텐츠가 보조 기술과 함께 작동해야 함 |
준수 수준
| 수준 | 요구사항 | 목표 |
|---|---|---|
| A | 최소 접근성 | 통과해야 함 |
| AA | 표준 준수 | 통과해야 함 (많은 관할권에서 법적 요구사항) |
| AAA | 향상된 접근성 | 있으면 좋음 |
지각 가능
대체 텍스트 (1.1)
이미지에는 대체 텍스트가 필요합니다:
<!-- ❌ 대체 텍스트 누락 -->
<img src="chart.png">
<!-- ✅ 설명적인 대체 텍스트 -->
<img src="chart.png" alt="3분기 매출이 40% 증가했음을 보여주는 막대 차트">
<!-- ✅ 장식용 이미지 (빈 대체 텍스트) -->
<img src="decorative-border.png" alt="" role="presentation">
<!-- ✅ 더 긴 설명이 필요한 복잡한 이미지 -->
<figure>
<img src="infographic.png" alt="2024 시장 동향 인포그래픽"
aria-describedby="infographic-desc">
<figcaption id="infographic-desc">
<!-- 상세 설명 -->
</figcaption>
</figure>
아이콘 버튼에는 접근 가능한 이름이 필요합니다:
<!-- ❌ 접근 가능한 이름 없음 -->
<button><svg><!-- 메뉴 아이콘 --></svg></button>
<!-- ✅ aria-label 사용 -->
<button aria-label="메뉴 열기">
<svg aria-hidden="true"><!-- 메뉴 아이콘 --></svg>
</button>
<!-- ✅ 시각적으로 숨겨진 텍스트 사용 -->
<button>
<svg aria-hidden="true"><!-- 메뉴 아이콘 --></svg>
<span class="visually-hidden">메뉴 열기</span>
</button>
시각적으로 숨겨진 클래스:
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
색상 대비 (1.4.3, 1.4.6)
| 텍스트 크기 | AA 최소 | AAA 향상 |
|---|---|---|
| 일반 텍스트 (< 18px / < 14px bold) | 4.5:1 | 7:1 |
| 큰 텍스트 (≥ 18px / ≥ 14px bold) | 3:1 | 4.5:1 |
| UI 컴포넌트 및 그래픽 | 3:1 | 3:1 |
/* ❌ 낮은 대비 (2.5:1) */
.low-contrast {
color: #999;
background: #fff;
}
/* ✅ 충분한 대비 (7:1) */
.high-contrast {
color: #333;
background: #fff;
}
/* ✅ 초점 상태도 대비가 필요합니다 (배경 대비 3:1, WCAG 1.4.11) */
:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
}
색상에만 의존하지 마세요:
<!-- ❌ 색상만 오류를 나타냄 -->
<input class="error-border">
<style>.error-border { border-color: red; }</style>
<!-- ✅ 색상 + 아이콘 + 텍스트 -->
<div class="field-error">
<input aria-invalid="true" aria-describedby="email-error">
<span id="email-error" class="error-message">
<svg aria-hidden="true"><!-- 오류 아이콘 --></svg>
유효한 이메일 주소를 입력해주세요
</span>
</div>
미디어 대체 수단 (1.2)
<!-- 자막이 있는 비디오 -->
<video controls>
<source src="video.mp4" type="video/mp4">
<track kind="captions" src="captions.vtt" srclang="en" label="English" default>
<track kind="descriptions" src="descriptions.vtt" srclang="en" label="Descriptions">
</video>
<!-- 대본이 있는 오디오 -->
<audio controls>
<source src="podcast.mp3" type="audio/mp3">
</audio>
<details>
<summary>대본</summary>
<p>전체 대본 텍스트...</p>
</details>
운용 가능
키보드 접근성 (2.1)
모든 기능은 키보드로 접근 가능해야 합니다. <button>, <a href>, 양식 컨트롤 같은 기본 대화형 요소를 선호하세요. 이러한 요소는 Enter/Space 활성화, 초점, 보조 기술 시맨틱을 무료로 처리합니다. 기본 요소를 사용할 수 없는 경우에만 수동 키보드 처리를 추가하세요.
<!-- ❌ 클릭만 가능한 비대화형 요소: 초점 불가능, 키보드 활성화 없음 -->
<div class="card" onclick="handleAction()">열기</div>
<!-- ✅ 가장 좋음: 기본 버튼 사용 -->
<button type="button" onclick="handleAction()">열기</button>
// ✅ 비대화형 요소(예: role="button"을 가진 div)를 반드시 사용해야 하는 경우,
// 초점 가능하게 만들고 키보드 활성화를 처리하십시오. 기본 <button>에는 이것을 추가하지 마십시오.
// Enter/Space가 이미 클릭을 발생시키므로 두 번 트리거됩니다.
element.setAttribute('role', 'button');
element.setAttribute('tabindex', '0');
element.addEventListener('click', handleAction);
element.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleAction();
}
});
키보드 트랩이 없어야 합니다. 사용자는 모든 컴포넌트에서 Tab을 이용해 들어오고 나갈 수 있어야 합니다. 대화상자에는 모달 초점 트랩 패턴을 사용하세요. 기본 <dialog> 요소는 이를 자동으로 처리합니다.
초점 가시성 (2.4.7)
/* ❌ 절대 초점 아웃라인을 제거하지 마세요 */
*:focus { outline: none; }
/* ✅ 키보드 전용 초점에는 :focus-visible 사용 */
:focus {
outline: none;
}
:focus-visible {
outline: 2px solid currentColor; /* 텍스트 색상 상속 -> 이미 대비 확인됨 */
outline-offset: 2px;
}
/* ✅ 또는 브랜드 색상을 선택하고 모든 배경에 대해 ≥3:1 대비를 확인 */
button:focus-visible {
box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.5);
}
초점이 가려지지 않음 (2.4.11) — 2.2에서 신규
요소가 키보드 초점을 받을 때, 고정 헤더, 푸터, 겹치는 패널과 같은 작성자가 만든 다른 콘텐츠에 의해 완전히 가려져서는 안 됩니다. 레벨 AAA(2.4.12)에서는 초점이 있는 요소의 어떤 부분도 숨겨져서는 안 됩니다.
/* ✅ 초점이 있는 요소로 스크롤할 때 고정 헤더를 고려 */
:target {
scroll-margin-top: 80px;
}
/* ✅ 초점이 있는 항목이 고정/스티키 막대를 비우도록 함 */
:focus {
scroll-margin-top: 80px;
scroll-margin-bottom: 60px;
}
건너뛰기 링크 (2.4.1)
키보드 사용자가 반복되는 탐색을 건너뛸 수 있도록 건너뛰기 링크를 제공하세요. 전체 마크업과 스타일은 건너뛰기 링크 패턴을 참조하세요.
대상 크기 (2.5.8) — 2.2에서 신규
대화형 대상은 최소 24 × 24 CSS 픽셀(AA)이어야 합니다. 예외: 인라인 텍스트 링크, 브라우저가 크기를 제어하는 요소, 경계 상자의 중심에 있는 24px 원이 다른 대상과 겹치지 않는 대상.
/* ✅ 최소 대상 크기 */
button,
[role="button"],
input[type="checkbox"] + label,
input[type="radio"] + label {
min-width: 24px;
min-height: 24px;
}
/* ✅ 편안한 대상 크기 (권장 44×44) */
.touch-target {
min-width: 44px;
min-height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
}
드래그 동작 (2.5.7) — 2.2에서 신규
드래그가 필요한 모든 작업에는 단일 포인터 대안(예: 버튼, 입력)이 있어야 합니다. 정렬 가능한 목록 예제는 드래그 동작 패턴을 참조하세요.
시간 제한 (2.2)
// 사용자가 시간 제한을 연장할 수 있도록 허용
function showSessionWarning() {
const modal = createModal({
title: '세션 만료 예정',
content: '2분 후에 세션이 만료됩니다.',
actions: [
{ label: '세션 연장', action: extendSession },
{ label: '로그아웃', action: logout }
],
timeout: 120000
});
}
동작 (2.3)
/* 축소된 동작 선호 설정 존중 */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
이해 가능
페이지 언어 (3.1.1)
<!-- ❌ 언어 지정되지 않음 -->
<html>
<!-- ✅ 언어 지정됨 -->
<html lang="ko">
<!-- ✅ 페이지 내에서 언어 변경 -->
<p>프랑스어로 인사말은 <span lang="fr">bonjour</span>입니다.</p>
일관된 탐색 (3.2.3)
<!-- 탐색은 페이지 전체에서 일관되어야 함 -->
<nav aria-label="메인">
<ul>
<li><a href="/" aria-current="page">홈</a></li>
<li><a href="/products">제품</a></li>
<li><a href="/about">소개</a></li>
</ul>
</nav>
일관된 도움말 (3.2.6) — 2.2에서 신규
도움말 메커니즘(연락처 정보, 채팅 위젯, FAQ 링크, 자가 도움말 옵션)이 여러 페이지에서 반복되는 경우, 매번 동일한 상대적 순서로 나타나야 합니다. 일관된 배치에 의존하는 사용자가 매 페이지마다 도움말을 찾느라 헤매지 않도록 합니다.
양식 레이블 (3.3.2)
모든 입력에는 프로그래밍 방식으로 연결된 레이블이 필요합니다. 명시적, 암시적 및 지침 예제는 양식 레이블 패턴을 참조하세요.
오류 처리 (3.3.1, 3.3.3)
role="alert" 또는 aria-live를 사용하여 스크린 리더에 오류를 알리고, 잘못된 필드에 aria-invalid="true"를 설정하며, 제출 시 첫 번째 오류에 초점을 맞춥니다. 전체 마크업과 JS는 오류 처리 패턴을 참조하세요.
중복 입력 방지 (3.3.7) — 2.2에서 신규
사용자가 동일한 세션에서 이미 제공한 정보를 다시 입력하도록 강요하지 마세요. 이전 단계에서 자동으로 채우거나, 이전에 입력한 값에서 선택할 수 있도록 하세요. 예외: 보안 재확인 및 만료된 콘텐츠.
<!-- ✅ 청구지 주소를 배송지 주소로 자동 채움 -->
<fieldset>
<legend>배송지 주소</legend>
<label>
<input type="checkbox" id="same-as-billing" checked>
청구지 주소와 동일
</label>
<!-- 체크되면 필드가 자동으로 채워짐 -->
</fieldset>
접근 가능한 인증 (3.3.8) — 2.2에서 신규
로그인 흐름은 인지 기능 테스트(예: 비밀번호 기억, 퍼즐 풀기)에 의존해서는 안 됩니다. 단, 다음 중 하나 이상이 있는 경우는 예외입니다:
- 복사-붙여넣기 또는 자동 채우기 메커니즘 사용 가능
- 대체 방법 존재 (예: 패스키, SSO, 이메일 링크)
- 테스트가 객체 인식 또는 개인 콘텐츠를 사용함 (AA만 해당; AAA는 이 예외를 제거)
<!-- ✅ 비밀번호 필드에 붙여넣기 허용 -->
<input type="password" id="password" autocomplete="current-password">
<!-- ✅ 비밀번호 없는 대안 제공 -->
<button type="button">패스키로 로그인</button>
<button type="button">로그인 링크를 이메일로 보내기</button>
견고함
ARIA 사용법 (4.1.2)
기본 요소 선호:
<!-- ❌ div에 ARIA 역할 사용 -->
<div role="button" tabindex="0">클릭하세요</div>
<!-- ✅ 기본 버튼 -->
<button>클릭하세요</button>
<!-- ❌ ARIA 체크박스 -->
<div role="checkbox" aria-checked="false">옵션</div>
<!-- ✅ 기본 체크박스 -->
<label><input type="checkbox"> 옵션</label>
ARIA가 필요한 경우, 올바른 역할과 상태를 사용하세요. 전체 탭 목록 예제는 ARIA 탭 패턴을 참조하세요.
라이브 리전 (4.1.3)
초점을 이동하지 않고 동적 콘텐츠 변경 사항을 알리려면 aria-live 리전을 사용하세요. 마크업과 showNotification() 헬퍼는 라이브 리전 패턴을 참조하세요.
테스트 체크리스트
자동화 테스트
에이전트에 실패한 렌더링된 노드를 직접 반환하는 실시간 Lighthouse 감사를 선호하세요. Chrome DevTools MCP에서는 lighthouse_audit입니다. 그렇지 않으면:
# Lighthouse 접근성 감사
npx lighthouse https://example.com --only-categories=accessibility
# axe-core
npm install @axe-core/cli -g
axe https://example.com
수동 테스트
- 키보드 탐색: Tab으로 전체 페이지를 순회하고 Enter/Space로 활성화
- 스크린 리더: VoiceOver(Mac), NVDA(Windows) 또는 TalkBack(Android)으로 테스트
- 확대: 200% 확대에서 콘텐츠 사용 가능
- 고대비: Windows 고대비 모드로 테스트
- 축소된 동작:
prefers-reduced-motion: reduce로 테스트 - 초점 순서: 논리적이고 시각적 순서를 따름
- 대상 크기: 대화형 요소가 24×24px 최소 기준 충족
VoiceOver 및 NVDA 단축키는 스크린 리더 명령 참조를 참조하세요.
영향별 일반적인 문제
심각 (즉시 수정)
- 양식 레이블 누락
- 이미지 대체 텍스트 누락
- 불충분한 색상 대비
- 키보드 트랩
- 초점 표시기 없음
중요 (출시 전 수정)
- 페이지 언어 누락
- 제목 구조 누락
- 설명적이지 않은 링크 텍스트
- 자동 재생 미디어
- 건너뛰기 링크 누락
보통 (조만간 수정)
- 아이콘에 ARIA 레이블 누락
- 일관되지 않은 탐색
- 오류 식별 누락
- 제어 장치 없는 시간 제한
- 랜드마크 영역 누락


