전 세계 인구의 16%가 장애를 경험하고 있다
세계보건기구(WHO)의 2023년 보고서에 따르면, 전 세계 인구의 약 16%(13억 명)가 어떤 형태로든 장애를 경험하고 있습니다. 한국 기준으로도 등록 장애인만 264만 명, 고령 인구까지 포함하면 웹 접근성이 필요한 사용자는 훨씬 더 많습니다.
그런데 웹 접근성은 장애인만을 위한 것이 아닙니다. 한 손으로 스마트폰을 조작하는 사람, 시끄러운 환경에서 자막이 필요한 사람, 밝은 햇빛 아래에서 화면을 보는 사람 — 모두 접근성의 수혜자입니다. Microsoft의 "Inclusive Design" 연구에 따르면, 접근성을 고려한 디자인은 영구적 장애, 일시적 장애, 상황적 제약 모두에게 도움을 줍니다.
아래에서는 WCAG 2.2 기준으로 프론트엔드 개발자가 실무에서 적용해야 할 접근성 원칙과 구체적인 코드 패턴을 정리합니다.
---
WCAG 2.2의 네 가지 원칙 — POUR
WCAG(Web Content Accessibility Guidelines)는 W3C가 제정한 국제 웹 접근성 표준입니다. 2023년 10월 발표된 WCAG 2.2는 네 가지 원칙을 기반으로 합니다:
인식 가능(Perceivable): 모든 콘텐츠는 사용자가 인식할 수 있어야 합니다. 이미지에는 대체 텍스트를, 동영상에는 자막을 제공하는 것이 대표적인 예시입니다.
운용 가능(Operable): 키보드만으로도 모든 기능을 사용할 수 있어야 합니다. 마우스를 사용할 수 없는 사용자를 위한 원칙이죠.
이해 가능(Understandable): 콘텐츠와 인터페이스가 이해하기 쉬워야 합니다. 입력 오류 시 명확한 안내 메시지를 제공하는 것이 여기에 해당합니다.
견고함(Robust): 다양한 보조 기술(스크린 리더, 화면 확대기 등)과 호환되어야 합니다.
적합성 수준은 A(최소), AA(권장), AAA(최고) 세 단계로 나뉘며, 대부분의 법적 요구사항과 실무 가이드라인은 AA 수준을 기준으로 합니다.
---
시맨틱 HTML — 접근성의 80%를 해결하는 첫 단추
접근성 문제의 상당수는 시맨틱 HTML만 올바르게 사용해도 해결됩니다. WebAIM의 2024년 Million 분석에 의하면, 상위 100만 개 웹사이트의 96.3%에서 접근성 오류가 발견되었고, 그중 가장 흔한 오류가 이미지 대체 텍스트 누락(54.5%)과 비어 있는 링크(48.6%)였습니다.
올바른 시맨틱 구조
```html <!-- ❌ div 남용 --> <div class="header"> <div class="nav"> <div class="nav-item" onclick="navigate()">메뉴</div> </div> </div>
<!-- ✅ 시맨틱 HTML --> <header> <nav aria-label="주 메뉴"> <ul> <li><a href="/about">소개</a></li> <li><a href="/blog">블로그</a></li> <li><a href="/contact">문의</a></li> </ul> </nav> </header> ```
`<header>`, `<nav>`, `<main>`, `<aside>`, `<footer>` 같은 랜드마크 요소를 사용하면, 스크린 리더 사용자가 페이지 구조를 빠르게 파악하고 원하는 섹션으로 바로 이동할 수 있습니다.
제목 태그 계층 구조
```html <!-- ❌ 잘못된 제목 순서 --> <h1>사이트 제목</h1> <h3>섹션 제목</h3> <!-- h2를 건너뜀! --> <h5>소제목</h5>
<!-- ✅ 올바른 계층 구조 --> <h1>웹 접근성 가이드</h1> <h2>WCAG 원칙</h2> <h3>인식 가능</h3> <h3>운용 가능</h3> <h2>실전 적용</h2> <h3>시맨틱 HTML</h3> ```
제목 레벨을 건너뛰면 스크린 리더 사용자가 문서 구조를 파악하기 어려워집니다. 시각적 크기는 CSS로 조절하되, HTML 계층은 반드시 순서를 지켜야 합니다.
---
ARIA — 시맨틱 HTML을 보완하는 도구
ARIA(Accessible Rich Internet Applications)는 HTML만으로 전달할 수 없는 의미를 보조 기술에 제공합니다. 하지만 ARIA의 첫 번째 규칙은 역설적이게도 "가능하면 ARIA를 쓰지 마라"입니다. 네이티브 HTML 요소가 이미 접근성을 내장하고 있기 때문이죠.
ARIA가 필요한 실제 상황
```typescript // 커스텀 드롭다운 메뉴 function Dropdown({ label, items, isOpen, onToggle }) { return ( <div className="dropdown"> <button aria-haspopup="listbox" aria-expanded={isOpen} aria-label={label} onClick={onToggle} > {label} </button>
{isOpen && ( <ul role="listbox" aria-label={`${label} 옵션`}> {items.map((item, i) => ( <li key={item.id} role="option" aria-selected={item.selected} tabIndex={0} > {item.name} </li> ))} </ul> )} </div> ); } ```
라이브 영역 — 동적 콘텐츠 알림
SPA에서 페이지 이동 없이 콘텐츠가 변경될 때, 스크린 리더에게 변경 사항을 알려야 합니다.
```typescript // 토스트 알림 function Toast({ message }: { message: string }) { return ( <div role="alert" aria-live="assertive" // 즉시 알림 aria-atomic="true" // 전체 내용을 다시 읽음 className="toast" > {message} </div> ); }
// 검색 결과 카운트 function SearchResults({ count }: { count: number }) { return ( <div aria-live="polite" aria-atomic="true"> {count}개의 결과를 찾았습니다. </div> ); } ```
`aria-live="polite"`은 현재 읽고 있는 내용이 끝난 후 알려주고, `"assertive"`는 즉시 알려줍니다. 에러 메시지나 긴급 알림에는 `assertive`를, 검색 결과 업데이트 같은 보조적 정보에는 `polite`을 사용하세요.
---
키보드 내비게이션 — 마우스 없는 세상
모든 인터랙티브 요소는 키보드만으로 접근 가능해야 합니다. 이것은 WCAG AA의 필수 요구사항입니다.
포커스 관리
```css / ❌ 포커스 링 제거 — 절대 금지! / *:focus { outline: none; }
/ ✅ 포커스 링 커스터마이징 / :focus-visible { outline: 3px solid #2563eb; outline-offset: 2px; border-radius: 4px; }
/ 마우스 클릭 시에는 포커스 링 숨기기 / :focus:not(:focus-visible) { outline: none; } ```
`:focus-visible`은 키보드로 포커스가 이동했을 때만 스타일을 적용하므로, 마우스 사용자에게는 포커스 링이 보이지 않으면서도 키보드 사용자에게는 명확한 시각적 피드백을 제공합니다.
모달 포커스 트랩
모달이 열리면 포커스가 모달 내부에 갇혀야 하고, 닫히면 원래 위치로 돌아가야 합니다.
```typescript function Modal({ isOpen, onClose, children }) { const modalRef = useRef<HTMLDivElement>(null); const previousFocus = useRef<HTMLElement | null>(null);
useEffect(() => { if (isOpen) { previousFocus.current = document.activeElement as HTMLElement; modalRef.current?.focus(); } else { previousFocus.current?.focus(); // 원래 위치로 복원 } }, [isOpen]);
function handleKeyDown(e: KeyboardEvent) { if (e.key === 'Escape') onClose(); if (e.key === 'Tab') { const focusable = modalRef.current?.querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ); if (!focusable?.length) return; const first = focusable[0] as HTMLElement; const last = focusable[focusable.length - 1] as HTMLElement;
if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); } else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); } } }
if (!isOpen) return null; return ( <div role="dialog" aria-modal="true" aria-label="설정" ref={modalRef} tabIndex={-1} onKeyDown={handleKeyDown} > {children} <button onClick={onClose}>닫기</button> </div> ); } ```
---
색상과 대비 — 시각적 접근성
명도 대비율
WCAG AA 기준으로 일반 텍스트는 4.5:1, 큰 텍스트(18pt 이상 또는 14pt 굵게)는 3:1 이상의 명도 대비율이 필요합니다.
```css / ❌ 낮은 대비 — 2.8:1 / .low-contrast { color: #999999; background: #ffffff; }
/ ✅ 충분한 대비 — 7.4:1 / .good-contrast { color: #374151; background: #ffffff; } ```
색상만으로 정보를 전달하지 않기
색맹 사용자(남성의 약 8%, 여성의 약 0.5%)를 위해 색상 외에 아이콘, 텍스트, 패턴 등 추가 시각 단서를 제공해야 합니다.
```typescript // ❌ 색상만으로 상태 표시 <span style={{ color: isValid ? 'green' : 'red' }}> {isValid ? '유효' : '무효'} </span>
// ✅ 색상 + 아이콘 + 텍스트 <span style={{ color: isValid ? '#16a34a' : '#dc2626' }}> {isValid ? '✓ 유효합니다' : '✗ 무효합니다'} </span> ```
---
폼 접근성 — 가장 흔한 실수 영역
라벨과 입력 필드 연결
```html <!-- ❌ 라벨 없는 입력 필드 --> <input type="email" placeholder="이메일 주소" />
<!-- ✅ 명시적 라벨 연결 --> <label for="email">이메일 주소</label> <input type="email" id="email" aria-describedby="email-hint" /> <p id="email-hint">예: [email protected]</p> ```
`placeholder`는 라벨을 대체할 수 없습니다. placeholder는 입력 시 사라지기 때문에, 사용자가 무엇을 입력해야 하는지 잊을 수 있고, 스크린 리더에서 일관되게 읽히지 않는 문제가 있습니다.
에러 메시지 접근성
```typescript function FormField({ label, error, id, ...props }) { const errorId = `${id}-error`; return ( <div> <label htmlFor={id}>{label}</label> <input id={id} aria-invalid={!!error} aria-describedby={error ? errorId : undefined} {...props} /> {error && ( <p id={errorId} role="alert" className="text-red-500"> {error} </p> )} </div> ); } ```
---
접근성 테스트 도구와 워크플로우
접근성을 체계적으로 관리하려면, 개발 프로세스의 각 단계에 테스트를 통합해야 합니다.
개발 중: ESLint의 `eslint-plugin-jsx-a11y`는 코드 작성 시점에 접근성 문제를 잡아줍니다. axe-core 기반의 브라우저 확장 프로그램도 필수 도구입니다.
CI/CD 단계: Lighthouse CI나 pa11y를 파이프라인에 추가하면, 접근성 점수가 기준 이하일 때 빌드를 실패시킬 수 있습니다.
수동 테스트: 자동 도구만으로는 접근성 문제의 약 30~40%만 감지할 수 있다는 연구(Deque Systems, 2023)가 있습니다. 키보드만으로 전체 사이트를 탐색해보고, VoiceOver(macOS)나 NVDA(Windows) 같은 스크린 리더로 직접 테스트하는 것이 중요합니다.
웹 접근성은 "추가 기능"이 아니라 기본 품질입니다. 처음부터 접근성을 고려하면 추가 비용이 거의 없지만, 나중에 수정하려면 전체 코드를 다시 만져야 할 수도 있습니다. 시맨틱 HTML을 올바르게 사용하는 것부터 시작해보세요. 그것만으로도 접근성의 절반 이상을 달성할 수 있으니까요.