도입 스토리
"감사 리포트 완성했어요." 김개발이 37개의 오류 목록을 펼쳤습니다.
"잘 했어요. 이제 어떻게 할 거예요?"
"다 고쳐야 하는 거 아닌가요?"
"당연히 그래야 하지만, 순서가 있어요. Critical 11개부터 시작해요. 그리고 개선 전후를 반드시 비교해야 해요 — Before/After 코드와 스크린리더 체험 결과를 문서화하는 거예요. 개선이 실제로 효과가 있었는지 증명해야 하니까요."
핵심 개념 설명
우선순위별 개선 계획
## 스프린트 계획 (2주)
### 이번 주 — Critical 11개 수정
- [ ] [A-001~A-005] 이미지 alt 텍스트 5개 추가 (0.5일)
- [ ] [A-006] 포커스 스타일 전역 수정 (0.5일)
- [ ] [A-007~A-009] 폼 레이블 3개 연결 (1일)
- [ ] [A-010] 모달 포커스 트랩 구현 (1일)
- [ ] [A-011] 스킵 내비게이션 추가 (0.5일)
### 다음 주 — Serious 15개 수정
- [ ] [A-012~A-019] 색상 대비 CSS 수정 (1일)
- [ ] [A-020~A-026] 링크 텍스트 개선 (1일)
Before/After 문서화 패턴
각 수정 사항을 Before/After로 기록합니다.
[A-001] 이미지 alt 텍스트 — 메인 배너:
<!-- Before: alt 없음 --><img src="/images/hero-spring.jpg" class="hero-banner"><!-- After: 내용 설명 alt --><img src="/images/hero-spring.jpg" alt="2026 봄 신상품 출시 — 전 품목 20~50% 할인, 4월 30일까지" class="hero-banner">
스크린리더 읽기 비교:
- Before: "이미지. hero-spring.jpg."
- After: "이미지. 2026 봄 신상품 출시 — 전 품목 20~50% 할인, 4월 30일까지."
[A-006] 포커스 스타일 — 전역 CSS:
/* Before: 포커스 스타일 제거 */* { outline: none;}/* After: 키보드 탐색 시 포커스 표시 */:focus-visible { outline: 3px solid #005FCC; outline-offset: 2px;}/* 다크 배경에서의 포커스 */.dark-bg :focus-visible { outline-color: #66AAFF;}
[A-010] 모달 포커스 트랩:
// Before: 포커스 트랩 없음function openModal() { document.getElementById('modal').style.display = 'block'; // 포커스 이동 없음}// After: 포커스 트랩과 복원const modal = new Modal(document.getElementById('delete-modal'));document.getElementById('open-modal-btn').addEventListener('click', function() { modal.open(this); // opener 버튼 전달});// Modal 클래스는 자동으로:// - 첫 번째 포커스 가능 요소로 포커스 이동// - Tab/Shift+Tab 트랩 적용// - Escape로 닫기 + opener로 포커스 복원
회귀 테스트
수정 후 기존 기능이 깨지지 않았는지 확인합니다.
// 회귀 테스트 — 모달describe('모달 접근성', () => { test('열기 시 포커스 이동', () => { render(<App />); userEvent.click(screen.getByRole('button', { name: '게시글 삭제' })); expect(screen.getByRole('dialog')).toBeInTheDocument(); expect(document.activeElement).toHaveAttribute('type', 'button'); }); test('닫기 시 포커스 복원', () => { render(<App />); const opener = screen.getByRole('button', { name: '게시글 삭제' }); userEvent.click(opener); userEvent.keyboard('{Escape}'); expect(document.activeElement).toBe(opener); }); test('Tab 트랩', async () => { render(<App />); userEvent.click(screen.getByRole('button', { name: '게시글 삭제' })); // 마지막 버튼에서 Tab → 첫 번째로 순환 const buttons = screen.getAllByRole('button'); const lastBtn = buttons[buttons.length - 1]; lastBtn.focus(); userEvent.keyboard('{Tab}'); expect(document.activeElement).toBe(buttons[0]); });});
개선 효과 측정
수정 전후 Lighthouse 점수를 비교합니다.
## 접근성 점수 변화
| 페이지 | 수정 전 | Critical 수정 후 | 전체 수정 후 |
|--------|---------|-----------------|------------|
| 홈 | 62 | 78 | 91 |
| 로그인 | 71 | 85 | 94 |
| 상품 목록 | 58 | 74 | 88 |
| 결제 | 49 | 73 | 92 |
### axe 오류 수
| 심각도 | 수정 전 | 수정 후 |
|--------|---------|--------|
| Critical | 11 | 0 |
| Serious | 15 | 2 |
| Moderate | 8 | 3 |
| Minor | 3 | 1 |
단계별 실습
따라하기: 실습용 접근성 개선 프로젝트
GitHub에서 접근성 문제가 있는 실습 템플릿을 클론합니다.
git clone https://github.com/sung2ne/web-a11y-practicecd web-a11y-practicenpm installnpm run dev
README에 발견해야 할 15개의 접근성 오류 목록이 있습니다. 각 오류를 수정하고 Before/After를 문서화합니다.
변형하기: 팀 코드 리뷰에 접근성 체크리스트 통합
PR 템플릿에 접근성 항목을 추가합니다.
<!-- .github/pull_request_template.md -->
## 접근성 체크리스트
새 UI 컴포넌트를 추가한 경우:
- [ ] 키보드만으로 모든 기능 사용 가능
- [ ] 포커스 스타일 보임
- [ ] 스크린리더 테스트 완료
- [ ] 색상 대비 4.5:1 이상 확인
- [ ] axe DevTools 오류 없음
정리와 확인
핵심 내용 요약
- 우선순위 순서: Critical → Serious → Moderate → Minor
- Before/After 문서화: 코드 변경 + 스크린리더 읽기 비교
- 회귀 테스트: 모달, 폼, 내비게이션 수정 후 테스트 추가
- 효과 측정: Lighthouse 점수 + axe 오류 수 추적
- PR 체크리스트: 팀 전체에 접근성 검토 내재화
확인 문제
문제 1. 접근성 개선 후 회귀 테스트가 필요한 이유는?
접근성 수정이 시각적 동작이나 JavaScript 로직에 영향을 미칠 수 있습니다.
특히 포커스 이동, 키보드 핸들러, ARIA 상태 변경 등은
기존 기능에 의도치 않은 부작용을 줄 수 있습니다.
문제 2. Lighthouse 점수가 동일해도 접근성이 달라질 수 있는 이유는?
Lighthouse는 자동화로 감지할 수 있는 항목만 측정합니다.
레이블 내용의 명확성, 읽기 순서의 논리성, 스크린리더 사용 경험 등
수동 테스트 영역은 점수에 포함되지 않습니다.
다음 챕터에서는 팀에게 접근성을 전파하고 문화를 만드는 방법을 다룹니다. 기술 문제가 아닌 조직 문제로서의 접근성을 다룹니다.