기능이 동작한다는 것과, 모두에게 동작한다는 것은 다른 문제다.
사용자가 마주하는 문제
마우스가 망가져서 키보드만으로 결제까지 가보려 했더니, 드롭다운이 열리지 않아 막혔습니다. 스크린리더로 글을 읽는 사용자에게 “여기를 클릭하세요”는 의미가 없습니다. 색약 사용자에게 빨간 에러 메시지가 회색 텍스트 옆에 놓이면 구분이 어렵습니다.
이런 문제는 시각적으로 멀쩡한 화면에서도 일어납니다. 일반적인 단위·컴포넌트·E2E 테스트는 잡지 못합니다. 접근성 테스트는 사용자가 화면을 어떻게 받아들이는가까지를 검증의 대상으로 가져옵니다.
무엇을 검증하나
접근성 영역은 자동 검사로 잡을 수 있는 것과 사람이 직접 확인해야 하는 것이 명확히 나뉩니다.
- 자동으로 잡히는 것 — 명도비, 누락된 대체 텍스트, 잘못된 ARIA 속성, 잘못된 헤딩 위계, 폼 레이블 누락 같은 구조적 문제
- 사람이 봐야 하는 것 — 키보드만으로 흐름이 끝까지 가는지, 스크린리더가 의미를 제대로 읽는지, 포커스가 시각적으로 명확한지, 동작 의도가 라벨과 일치하는지
전체 접근성 이슈의 약 30~40% 정도가 자동 검사로 잡힌다는 것이 일반적인 관찰입니다. 나머지는 사람이 직접 확인해야 합니다. 자동만 믿으면 60%를 놓치는 셈입니다.
자동 검사 - 도구와 예시
자동 검사 도구는 거의 모두 axe-core라는 공통 엔진을 기반으로 합니다. 따라서 어디서 돌리느냐가 차이입니다.
- 단위/컴포넌트 —
vitest-axe또는jest-axe로 컴포넌트 렌더링 결과를 검사 - Storybook —
@storybook/addon-a11y로 스토리별 자동 검사 - E2E —
@axe-core/playwright로 진짜 페이지를 검사 - CI 외부 — Lighthouse CI, Pa11y로 배포된 페이지를 정기 검사
Playwright + axe 예시입니다.
// 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"])
.analyze();
// 심각도 critical/serious인 위반만 차단
const blocking = results.violations.filter(v =>
v.impact === "critical" || v.impact === "serious"
);
expect(blocking).toEqual([]);
});
핵심은 두 가지입니다.
- 태그로 기준선을 정한다 (WCAG 2.0/2.1, 레벨 A/AA). 처음부터 AAA를 노리면 운영이 무너집니다
- 심각도로 차단 범위를 정한다. 모든 violation을 차단하면 PR이 끝없이 막힙니다. critical/serious부터 시작해 점진적으로 좁힙니다
수동 확인 - 무엇을, 어떻게
수동 확인은 한두 가지의 짧은 체크로 의외로 많은 것을 잡습니다.
- 키보드 전용 내비게이션 — Tab만으로 핵심 흐름을 끝까지 갈 수 있는가. 포커스가 보이는가. Tab 순서가 시각 순서와 일치하는가
- 스크린리더 듣기 — macOS는 VoiceOver(
Cmd + F5), Windows는 NVDA, 모바일은 TalkBack/VoiceOver. 화면을 보지 않고 들어보면 라벨과 의미가 맞는지 즉시 드러납니다 - 명도비 확인 — 디자인 단계에서부터 검증. Figma 플러그인이나 contrast-checker 활용
- 확대 200% — 텍스트만 확대했을 때 깨지지 않는가
이런 점검은 매 PR마다 할 필요는 없습니다. 새 화면이나 큰 흐름 변경이 있을 때 체크리스트로 한 바퀴 돌리는 식이 현실적입니다.
디자인 단계로 밀어내기
접근성에서 가장 비싼 시점은 “배포 직전에 발견”입니다. 가장 싼 시점은 디자인 단계입니다.
- 디자인 시스템의 토큰 자체에 명도비를 확보 (한 번 잡으면 모든 화면에 반영)
- 컴포넌트 라이브러리에 ARIA와 키보드 동작을 내장 (개별 화면이 신경 쓸 일이 없음)
- Figma 단계에서 색 대비, 글자 크기, 인터랙션 의도 검토
디자인 시스템이 잘 잡혀 있다면, 화면 단위 접근성 검사에서 잡히는 이슈가 크게 줄어듭니다. 이 시리즈에서 ep.03 컴포넌트와 짝지어 다루는 이유이기도 합니다.
언제, 어디서 실행하나
- 개발 중 — Storybook의 a11y addon으로 컴포넌트 작성과 함께 즉시 검사
- PR 단계 (CI) — 변경된 페이지에 대한 axe 검사. 심각도 critical/serious는 차단
- 릴리스 전 — 새 화면이나 큰 흐름 변경에 대해 수동 체크리스트
- 정기 점검 — Lighthouse CI 같은 도구로 운영 환경 주요 페이지를 주간 단위로 검사
도입 체크리스트
- 컴포넌트 단위 axe 테스트 설정 (vitest-axe 또는 storybook addon)
- CI에 axe Playwright 단계 추가 (랜딩, 로그인, 핵심 화면 최소 3개)
- 심각도 차단 기준 합의 (보통 critical/serious부터)
- 디자인 시스템 토큰의 명도비 점검 및 문서화
- 새 화면 릴리스 체크리스트에 키보드·스크린리더 점검 항목 추가
- 팀에 스크린리더 한 번씩 켜보는 시간 만들기 (한 번이라도 들어본 사람과 안 들어본 사람의 감각이 다릅니다)
흔한 함정
- 자동 검사만 신뢰한다 — 60%의 문제를 못 본 채로 “접근성 통과”라고 믿게 됩니다.
- 모든 violation을 차단한다 — 처음부터 모두 차단하면 PR이 막혀 결국 검사 자체를 꺼버리게 됩니다. 점진적으로 좁히는 게 답입니다.
- ‘aria-*‘를 무분별하게 붙인다 —
role="button"을<div>에 붙이는 것보다 그냥<button>을 쓰는 게 백배 낫습니다. 시맨틱 HTML이 항상 첫 선택지입니다. - 대체 텍스트 = 보이는 텍스트 — 이미지가 장식용이면 빈 alt(
alt=""), 정보가 있으면 그 정보를 글로. 화면의 visible text를 그대로 복사하면 스크린리더가 같은 내용을 두 번 읽습니다. - 포커스 스타일을 제거한다 —
outline: none만 쓰고 대체 스타일을 안 주면 키보드 사용자가 어디 있는지 모릅니다. 가장 흔하고 가장 치명적인 실수입니다.
참고 자료
- W3C. (2018). Web Content Accessibility Guidelines (WCAG) 2.1. https://www.w3.org/TR/WCAG21/
- Deque Systems. axe-core. https://github.com/dequelabs/axe-core
- Smashing Magazine. “Designing The Perfect Accordion”, (접근성 가이드라인 다수)
- MDN Web Docs. “Accessibility”. https://developer.mozilla.org/en-US/docs/Web/Accessibility
다음 편 예고
기능이 모두에게 도달했다면, 다음은 그 도달이 빠른가입니다. 사용자가 “빠르다”고 느끼는 속도를 무엇으로 측정하고, 회귀를 어떻게 추적할지를 다룹니다.