TechFeedTechFeed
Frontend

웹 접근성 체크리스트 2026 — WCAG 2.2 기준 프론트엔드 점검 38항목

WCAG 2.2 AA 기준으로 프론트엔드 프로젝트를 점검하는 38개 체크리스트. 시맨틱 HTML, 키보드 접근성, 색상 대비, ARIA, 폼/미디어 5개 영역별 통과 기준과 수정 코드를 제공한다.

"우리 서비스 접근성 괜찮은 거 맞아?" — 이 질문에 자신 있게 답할 수 있는 프론트엔드 팀은 드물다. Lighthouse 점수 100이 나와도 스크린 리더 사용자가 장바구니에 상품을 담지 못하는 경우가 실제로 발생한다.

이 글은 WCAG 2.2 AA 기준으로 프론트엔드 프로젝트를 점검할 수 있는 38개 체크리스트를 제공한다. 항목마다 통과/실패 판단 기준과 수정 코드를 함께 담았다. 위에서 아래로 순서대로 점검하면, 접근성 감사 보고서의 80% 이상을 커버할 수 있다.

이 체크리스트가 필요한 경우
  • B2B SaaS에서 접근성 준수 요구가 계약 조건에 들어온 팀
  • 공공기관/교육기관 대상 서비스를 개발하는 경우
  • Lighthouse 접근성 점수는 높은데 실제 보조기기 테스트를 해본 적 없는 경우
  • EU European Accessibility Act(2025.06 시행) 대응이 필요한 서비스

※ 이 글은 2026년 3월 기준, WCAG 2.2(2023.10 W3C 권고안) 기반으로 작성됐습니다.

이 체크리스트 사용법

38개 항목을 5개 영역으로 나눴다. 각 영역은 독립적이므로, 현재 가장 취약한 영역부터 시작해도 된다.

영역별 우선순위 판단 플로우
  1. 스크린 리더로 메인 페이지를 탐색해본 적 있는가? → 없다면 시맨틱 HTML부터
  2. 키보드만으로 핵심 기능(로그인, 결제 등)을 완료할 수 있는가? → 불가능하면 키보드 접근성부터
  3. 색상 대비 테스트를 한 적 있는가? → 없다면 색상 · 시각부터
  4. 동적 콘텐츠(모달, 토스트, 드롭다운)에 aria 속성이 있는가? → 없다면 ARIA · 동적 UI부터
  5. 위 4개가 모두 통과라면 → 폼 · 미디어 영역으로

각 항목의 형식은 다음과 같다:

  • ☐ 체크 항목 — 점검해야 할 내용
  • 판단 기준 — 통과/실패를 가르는 구체적 조건
  • 수정 방법 — 코드 수준의 해결책 (해당하는 경우)

팀에서 사용할 때는 이 항목들을 GitHub Issue 템플릿이나 PR 체크리스트로 복사해 쓰면 된다.

WCAG 2.2 적합성 수준 A, AA, AAA의 관계와 각 수준별 주요 항목을 보여주는 도표
WCAG 적합성 수준 구조 — 대부분의 법적 요구사항은 AA 수준이다 (출처: W3C WAI)

영역 1 — 시맨틱 HTML (8항목)

시맨틱 HTML은 접근성의 기초다. 이 영역이 무너지면 ARIA를 아무리 붙여도 보조기기가 페이지 구조를 이해하지 못한다. 특히 항목 1~3은 자동화 도구(axe, Lighthouse)로 즉시 잡히므로 가장 먼저 수정해야 한다.

axe-core로 시맨틱 HTML 자동 점검 (CI에 통합 가능)
npm install -D @axe-core/cli npx axe http://localhost:3000 --tags wcag2a,wcag2aa --exit

위 명령을 CI 파이프라인에 추가하면, WCAG A/AA 위반이 있을 때 빌드가 실패한다. --exit 플래그는 위반 발견 시 exit code 1을 반환한다.

영역 2 — 키보드 접근성 (8항목)

키보드 접근성을 가장 빠르게 테스트하는 방법: 마우스를 치우고 Tab 키만으로 메인 플로우(가입→로그인→핵심 기능→로그아웃)를 완료해보는 것이다. 5분이면 된다. 여기서 막히는 곳이 스크린 리더 사용자도 막히는 곳이다.

포커스 표시 — outline:none 대신 커스텀 포커스 링 적용
/* 모든 인터랙티브 요소에 적용 */ :focus-visible { outline: 2px solid var(--focus-color, #2563EB); outline-offset: 2px; border-radius: 4px; } /* 마우스 클릭 시에는 outline 숨김 */ :focus:not(:focus-visible) { outline: none; }

:focus-visible은 키보드 포커스일 때만 스타일을 적용한다. 마우스 클릭 시 outline이 보이는 것을 싫어하는 디자이너의 요구와 접근성을 동시에 만족시키는 방법이다. 2026년 기준 모든 주요 브라우저가 지원한다.

키보드 Tab 탐색 시 포커스 링이 각 UI 요소를 순서대로 이동하는 모습
포커스 링(focus ring)이 시각적으로 명확해야 키보드 사용자가 현재 위치를 파악할 수 있다 (출처: web.dev)

영역 3 — 색상과 시각 (7항목)

색상 대비는 자동화 도구로 가장 정확하게 잡을 수 있는 영역이다. Chrome DevTools의 CSS Overview 패널에서 페이지 전체의 대비 이슈를 한 번에 확인할 수 있다.

prefers-reduced-motion 대응 CSS
@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; } }

위 CSS를 전역 스타일에 한 번 추가하면 OS에서 "동작 줄이기"를 켠 사용자에게 모든 애니메이션이 비활성화된다. 0.01ms로 설정하는 이유는 0ms보다 animationend 이벤트가 안정적으로 발생하기 때문이다.

영역 4 — ARIA와 동적 UI (8항목)

ARIA의 첫 번째 규칙: "ARIA를 쓰지 않을 수 있으면 쓰지 마라." 네이티브 HTML 요소가 이미 올바른 역할(role)과 상태(state)를 갖고 있다. ARIA는 네이티브로 해결할 수 없는 커스텀 위젯에만 사용해야 한다.

가장 흔한 실수: <div onclick="...">role="button", tabindex="0", onkeydown을 모두 추가하는 것. 그냥 <button>을 쓰면 이 모든 게 무료다.

토스트 알림을 스크린 리더에 전달하는 aria-live 패턴
<!-- HTML: 페이지에 항상 존재하는 라이브 영역 --> <div id="toast-region" aria-live="polite" aria-atomic="true"></div> <!-- JS: 토스트 메시지 삽입 --> <script> function showToast(message) { const region = document.getElementById('toast-region'); region.textContent = message; // 시각적 토스트 UI도 별도 렌더링 } </script>

aria-live="polite"는 현재 읽고 있는 내용이 끝난 후 변경사항을 알려준다. 긴급한 에러에는 aria-live="assertive"를 사용하되, 남용하면 사용자 경험이 나빠진다.

영역 5 — 폼과 미디어 (7항목)

폼 접근성은 전환율과 직결된다. 접근성이 좋은 폼은 보조기기 사용자뿐 아니라 모든 사용자에게 더 쉬운 폼이다. 에러 메시지가 명확하고 필드와 연결되어 있으면 일반 사용자의 폼 완료율도 올라간다.

접근성을 갖춘 폼 에러 처리 패턴 (React)
function EmailInput({ error }) { return ( <div> <label htmlFor="email">이메일 주소</label> <input id="email" type="email" autoComplete="email" required aria-invalid={!!error} aria-describedby={error ? 'email-error' : undefined} /> {error && ( <p id="email-error" role="alert" style={{ color: 'var(--error-color, #DC2626)' }}> {error} </p> )} </div> ); }

핵심 포인트: aria-invalid는 필드가 에러 상태임을 알리고, aria-describedby는 에러 메시지를 해당 필드에 연결한다. role="alert"는 에러가 나타나는 즉시 스크린 리더가 읽어준다.

Chrome DevTools Accessibility 패널에서 ARIA 트리를 확인하는 화면
Chrome DevTools의 Accessibility 탭에서 ARIA 트리 구조를 직접 확인할 수 있다 (출처: Chrome DevTools 문서)

자동화 도구와 수동 테스트 조합 전략

접근성 테스트의 불편한 진실: 자동화 도구는 전체 접근성 이슈의 30~40%만 잡는다(Deque 연구 기준). 나머지는 수동 테스트가 필수다. 다만, 그 30~40%가 가장 빈번하고 수정이 쉬운 항목이므로 자동화부터 시작하는 게 맞다.

도구별 커버리지
도구용도커버 영역
axe DevTools브라우저 확장 + CI시맨틱, 대비, ARIA 기본
Lighthouse빌트인 감사기본 접근성 점수
WAVE시각적 오버레이구조, 대비, 라벨
VoiceOver (macOS)스크린 리더 수동 테스트실제 사용자 경험
NVDA (Windows)스크린 리더 수동 테스트실제 사용자 경험
Playwright + axe-core로 접근성 테스트를 E2E에 통합
// tests/a11y.spec.ts import { test, expect } from '@playwright/test'; import AxeBuilder from '@axe-core/playwright'; test('메인 페이지 접근성 위반 없음', async ({ page }) => { await page.goto('/'); const results = await new AxeBuilder({ page }) .withTags(['wcag2a', 'wcag2aa', 'wcag22aa']) .analyze(); expect(results.violations).toEqual([]); }); test('로그인 폼 접근성 위반 없음', async ({ page }) => { await page.goto('/login'); const results = await new AxeBuilder({ page }) .include('#login-form') .analyze(); expect(results.violations).toEqual([]); });

Playwright + axe-core 조합은 CI에서 자동으로 접근성을 검증하는 가장 실용적인 방법이다. 페이지별로 테스트를 나누면, 어떤 페이지에서 어떤 위반이 생겼는지 PR 단계에서 바로 확인할 수 있다.

자주 놓치는 실수 5가지

체크리스트를 모두 통과해도 반복적으로 나타나는 패턴이 있다. 코드 리뷰에서 특히 주의해야 할 항목이다.

실수 1: aria-hidden="true"를 포커스 가능한 요소에 사용
모달을 닫을 때 모달 전체에 aria-hidden을 걸면, 안에 있는 포커스 가능 요소가 보이지 않는데 포커스는 잡히는 유령 상태가 된다. 모달은 DOM에서 제거하거나 display:none을 사용할 것.
실수 2: SVG 아이콘에 접근성 처리 누락
의미 있는 아이콘: <svg role="img" aria-label="설정">. 장식용 아이콘: <svg aria-hidden="true">. 아이콘 버튼이면 버튼 자체에 aria-label을 달 것.
실수 3: SPA 라우트 전환 시 포커스/알림 누락
페이지가 전환됐는데 스크린 리더에 아무 알림이 없으면, 사용자는 무슨 일이 일어났는지 모른다. 라우트 전환 시 새 페이지 제목을 aria-live 영역에 알리거나, 메인 콘텐츠에 포커스를 이동시킬 것.
실수 4: 커스텀 셀렉트(select) 컴포넌트
디자인 때문에 <select>를 div 기반으로 재구현하면서 키보드 내비게이션, ARIA 역할, 스크린 리더 호환을 모두 놓치는 경우가 많다. Radix UI, Headless UI 같은 접근성 내장 라이브러리를 사용하는 것이 현실적이다.
실수 5: 타임아웃이 있는 세션에 연장 옵션 없음
WCAG 2.2 기준 2.2.1: 시간 제한이 있는 경우 사용자가 연장하거나 해제할 수 있어야 한다. 인증 세션 만료 전 경고 모달과 연장 버튼을 제공할 것.
접근성a11yWCAGWCAG2.2프론트엔드체크리스트ARIA스크린리더키보드접근성웹표준

관련 포스트

Next.js 15 핵심 변경사항 총정리2026-02-15Tailwind CSS v4 — 무엇이 달라졌나2026-02-19Svelte 5 vs React 19 — 2026년 프론트엔드 프레임워크 비교2026-03-13CSS Container Queries 실전 가이드2026-03-13