EAA 발효 후 프론트엔드 접근성 점검 체크리스트 15가지 (유럽 접근성법 WCAG 2.1 AA 기준)
2025년 6월 28일, 슬랙에서 흘러온 메시지를 보다가 멈췄습니다. "EAA 발효됐는데, 우리 서비스 괜찮아?" 처음엔 "EU 법이니까 유럽에서 직접 사업하는 회사 얘기겠지" 싶었는데, 역외 적용 조항을 읽고 나서 생각이 달라졌습니다. EU 고객에게 서비스를 제공하는 기업이라면 본사가 서울에 있든 뉴욕에 있든 적용 대상이거든요. 직원 10명 초과 또는 연매출 200만 유로 초과라면, 우리 서비스가 해당됩니다. 글로벌 SaaS, 영어권 e커머스, EU 쪽으로 트래픽이 들어오는 서비스라면 "우리는 괜찮겠지"를 한 번 다시 검토해볼 필요가 있습니다.
접근성 자동화 도구가 잡아주는 이슈는 전체의 3040%에 불과합니다. 나머지 6070%는 코드를 직접 들여다봐야 보이는 것들이에요. 이 글에서는 WCAG 2.1 AA 기준으로 현업에서 가장 자주 빠뜨리는 항목 15가지를 체크리스트로 정리했습니다. 읽고 나면 오늘 당장 실행할 수 있는 3단계 액션 플랜도 가져갈 수 있습니다.
코드 예시가 HTML/CSS/React 중심이라 이 글은 프론트엔드 영역에 집중합니다. 백엔드나 풀스택 개발자라면 "우리 서비스 프론트가 이렇게 돼야 하는구나"를 파악하는 참조 자료로 활용해볼 수 있을 거예요.
EAA(European Accessibility Act, 유럽 접근성법)는 EU 27개 회원국 전체에 적용되는 디지털 접근성 의무화 법령입니다. 웹사이트, 모바일 앱, 전자상거래, 온라인 뱅킹, 티켓팅 시스템 등 디지털 서비스 전반을 포괄하고요. 이 법이 기술 기준으로 채택한 것이 EN 301 549 v3.2.1이고, 이 표준의 핵심이 WCAG 2.1 Level AA를 기반으로 합니다. 즉, WCAG 2.1 AA를 준수하면 EAA 적합성을 "추정(presumption of conformity)"받을 수 있어서, 실질적으로 우리가 맞춰야 할 기준선이 됩니다.
신규 디지털 서비스는 2025년 6월 28일부터 즉시 의무 적용이고, 기존 콘텐츠는 국가별로 전환 일정이 상이할 수 있지만 EU 기준으로 2030년 6월 28일까지 유예가 적용됩니다. 중요한 점은, "기존 콘텐츠"라도 신규 기능이 추가되는 순간 해당 기능은 즉시 적용 대상이 된다는 것입니다.
WCAG 2.1 AA의 4대 원칙 (POUR)
이 4가지 원칙을 먼저 이해해두면 뒤에 나오는 예시들이 왜 필요한지 맥락이 잡힙니다. 각 예시마다 어떤 원칙이 관여하는지 표시해 둘 테니 참조 틀로 활용해볼 수 있을 거예요.
원칙
영문
핵심 내용
인지 가능
Perceivable
텍스트 대안, 자막, 색상 대비, 이미지 alt
조작 가능
Operable
키보드 접근성, 충분한 시간 제공, 발작 유발 방지
이해 가능
Understandable
언어 설정, 오류 식별 및 안내
견고
Robust
보조 기술과의 호환성, 유효한 마크업
이 원칙들이 독립적인 것처럼 보여도, 실무에서는 서로 맞물리는 경우가 훨씬 많습니다. 폼 하나에 Perceivable(오류 메시지를 보여줘야 함) + Understandable(오류가 무엇인지 설명해야 함) + Operable(키보드만으로도 수정할 수 있어야 함) + Robust(스크린 리더가 읽을 수 있어야 함)가 동시에 적용되는 식이에요.
실전 적용
예시 1: 폼 — 가장 민원이 많이 생기는 곳
관련 원칙: Perceivable + Understandable + Robust
솔직히 저도 초반에 placeholder를 라벨 대용으로 쓰는 게 뭐가 문제인지 잘 몰랐습니다. 값을 입력하기 시작하면 placeholder가 사라지는데, 그러면 사용자는 이 필드에 뭘 입력해야 했는지 기억에 의존해야 합니다. 스크린 리더 사용자는 아예 필드 목적 자체를 모를 수도 있고요.
html
<!-- ❌ placeholder만 있는 경우 --><input type="email" placeholder="이메일 입력" /><!-- ✅ label + aria-describedby로 맥락까지 제공 --><label for="email">이메일 주소</label><input type="email" id="email" aria-describedby="email-hint" aria-required="true"/><span id="email-hint">example@domain.com 형식으로 입력해 주세요</span>
aria-required="true" vs HTML5 required: 비슷해 보이지만 차이가 있습니다. HTML5 required는 브라우저 기본 유효성 검사(submit 시 빈 값 블로킹)까지 트리거하고, aria-required="true"는 보조 기술에만 정보를 전달합니다. 커스텀 유효성 로직에서 required 속성 없이 aria-required="true"만 쓰면, 보조 기술에는 정보가 전달되지만 브라우저 기본 유효성 검사는 동작하지 않습니다.
오류 처리에서 한 가지 주의할 점이 있습니다. aria-live="polite" 컨테이너와 role="alert" span을 함께 쓰면 오류 메시지가 두 번 읽히는 중복 발화 문제가 생깁니다. role="alert"은 자체적으로 aria-live="assertive"를 내포하기 때문이에요. 둘 중 하나를 선택해 일관되게 사용하는 것이 좋습니다.
html
<!-- 방법 1: aria-live="polite" — 현재 작업이 끝난 뒤 읽어줌 (비긴급 안내에 적합) --><div aria-live="polite" aria-atomic="true" class="sr-only" id="form-status"> <!-- JS로 메시지 삽입 --></div><!-- 방법 2: role="alert" — 즉시 끊고 읽어줌, aria-live="assertive" 내포 (즉각 오류 알림에 적합) --><input type="email" id="email" aria-invalid="true" aria-describedby="email-error"/><span id="email-error" role="alert"> 올바른 이메일 형식이 아닙니다.</span>
sr-only 클래스: 시각적으로는 숨기되 스크린 리더에는 읽히는 패턴입니다. Tailwind CSS를 사용 중이라면 내장 sr-only 클래스가 있고, 그렇지 않다면 직접 추가할 수 있습니다.
즉시 끊고 읽어줌, aria-live="assertive" 동일 — 긴급 오류 알림에 적합
예시 2: 키보드 내비게이션과 포커스 관리
관련 원칙: Operable
키보드 접근성에서 가장 많이 저지르는 실수는 두 가지입니다. outline: none 전역 처리와 모달 포커스 트랩 누락이에요. 두 번째는 처음에 "모달에 포커스 가두는 게 뭐가 중요해?" 싶을 수 있는데, 직접 Tab 키만으로 모달을 써보면 바로 이해가 됩니다.
css
/* ❌ 절대 이렇게 하면 안 됩니다 */* { outline: none;}/* ✅ 시각적으로 커스터마이징하되 제거는 금물 */*:focus-visible { outline: 3px solid #0066cc; outline-offset: 2px; border-radius: 4px;}
:focus vs :focus-visible: :focus는 마우스 클릭 시에도 포커스 링이 표시되지만, :focus-visible은 키보드 탐색 시에만 보입니다. 마우스 사용자에게는 깔끔한 UI를, 키보드 사용자에게는 명확한 포커스 표시를 동시에 제공할 수 있어 현재 권장되는 방식입니다. IE11 지원이 필요 없는 현대 브라우저 환경에서는 안전하게 사용할 수 있습니다.
프로덕션 환경 권장 라이브러리: 위 코드는 동작 원리 이해용입니다. Shadow DOM 컨텍스트, 동적으로 추가되는 포커스 가능 요소, 브라우저 간 activeElement 차이 같은 엣지 케이스가 실제 서비스에서 꽤 많이 나옵니다. focus-trap-react나 @radix-ui/react-focus-scope 같이 이런 케이스들을 이미 처리한 검증된 라이브러리를 사용하는 것을 권장합니다.
예시 3: 시맨틱 HTML과 ARIA
관련 원칙: Robust + Perceivable
레거시 코드베이스에서 div와 span으로 도배된 마크업을 마주칠 때가 있습니다. 처음 이 상황에서 ARIA를 잔뜩 붙여 해결하려 했는데, 돌이켜보면 시맨틱 HTML로 교체할 수 있는 부분을 먼저 정리하는 게 맞는 순서였습니다.
ARIA 사용의 첫 번째 원칙은 "네이티브 HTML 요소를 먼저 써라"입니다. W3C가 공식적으로 명명한 **ARIA 사용의 첫 번째 규칙(First Rule of ARIA Use)**인데, <button>을 role="button" 붙인 <div>로 대체하면 키보드 이벤트 처리, 포커스 관리, Enter/Space 동작까지 직접 구현해야 합니다. 브라우저가 네이티브 요소에 기본 제공하는 모든 것을 처음부터 다시 만드는 셈이에요.
html
<!-- ❌ div에 role 붙이기 --><div role="button" tabindex="0" onclick="submit()" onkeydown="handleKey(event)"> 제출</div><!-- ✅ 그냥 button 쓰면 됩니다 --><button type="submit">제출</button>
예시 4: 색상 대비와 비색상 정보 전달
관련 원칙: Perceivable
색상 대비는 디자이너가 챙겨야 할 영역 같지만, 개발 단계에서 컬러 팔레트가 바뀌거나 다크 모드를 추가할 때 대비율이 다시 달라지는 경우가 많습니다. DevTools나 Lighthouse로 수시로 재확인하는 습관을 들여두면 좋습니다.
콘텐츠 유형
최소 대비율
일반 텍스트
4.5:1 이상
대형 텍스트 (18pt+ 또는 Bold 14pt+)
3:1 이상
UI 컴포넌트 및 상태 표시 (버튼 테두리, 포커스 링 등)
3:1 이상
색상만으로 정보를 전달하는 것도 피해야 합니다. "빨간 항목이 오류입니다"처럼 색상만 바꿔두면 색맹 사용자는 오류 여부를 파악하기 어렵습니다.
html
<!-- ❌ 색상만으로 오류 표시 --><span style="color: red;">이메일</span><!-- ✅ 아이콘 + 텍스트 병행 --><span> <span aria-hidden="true">⚠️</span> 이메일 <span class="error-label">(입력 오류)</span></span>
예시 5: 모바일 접근성과 자주 빠뜨리는 영역
관련 원칙: Perceivable + Operable
모바일 접근성은 데스크톱에 비해 유독 놓치기 쉽습니다. 특히 두 가지가 자주 빠지는데, 작업 속도가 빠를수록 더 쉽게 지나쳐버립니다.
터치 타겟 크기: WCAG 2.5.5(AAA)와 WCAG 2.2의 2.5.8(AA)에서 요구하는 최소 터치 타겟은 44×44px입니다. 손 떨림이 있거나 섬세한 조작이 어려운 사용자들에게 너무 작은 버튼은 큰 장벽이 됩니다.
이미지 alt 텍스트도 의외로 자주 빠지는 영역입니다. "이미지"나 파일명을 그대로 쓰는 코드를 꽤 자주 봅니다.
html
<!-- ❌ 의미 없는 alt --><img src="chart.png" alt="이미지" /><img src="photo.jpg" alt="photo.jpg" /><!-- ✅ 맥락을 설명하는 alt --><img src="chart.png" alt="2025년 1분기 월별 매출 추이. 3월에 전월 대비 23% 증가."/><!-- ✅ 장식용 이미지는 빈 alt로 스크린 리더가 건너뛰게 처리 --><img src="divider.png" alt="" role="presentation" />
alt 텍스트 판단 기준을 간단히 정리하면: 의미 있는 이미지는 이미지가 전달하는 정보를 구체적으로 기술하고, 장식용 이미지는 alt=""로 두고, 복잡한 차트나 다이어그램은 aria-describedby로 데이터 표나 상세 설명을 연결하는 것이 좋습니다.
도입 비용과 효과
장점
항목
내용
시장 확대
EU 내 약 1억 100만 명이 어떤 형태의 장애를 갖고 있어, 접근성 개선이 실질적인 사용자층 확대로 이어짐
UX 전반 향상
키보드 내비게이션, 명확한 콘텐츠 구조, 일관된 UI는 장애인뿐 아니라 모든 사용자 경험을 개선
SEO 효과
시맨틱 마크업, 대안 텍스트 등 접근성 개선 요소는 검색 엔진 인덱싱에도 긍정적으로 작용
기술 부채 감소
컴포넌트 단위 수정이 전체 앱에 반영되어 반복 패치 비용 절감
단점 및 주의사항
항목
내용
대응 방안
레거시 코드 리팩터링
기존 div 기반 UI를 시맨틱 구조로 전환하는 데 공수가 상당함
신규 컴포넌트부터 적용하고 레거시는 점진적으로 전환
자동화 도구의 한계
axe-core, Lighthouse 등이 탐지할 수 있는 이슈는 전체의 30~40%에 불과
자동화 스캔 + 주기적 수동 감사 병행 필수
디자인-개발 협업 비용
색상 대비, 포커스 상태, 터치 타겟 크기 등 디자인 단계부터 고려해야 해 초기 시간이 늘어남
Figma A11y Annotation Kit 등으로 디자인 단계에서 접근성 명세 포함
지속적 모니터링 부담
새 기능 추가·릴리스마다 접근성 회귀 테스트 필요
CI/CD 파이프라인에 jest-axe, Pa11y 통합
법적 리스크
국가별로 집행 방식과 제재 수준이 상이하며, 일부 국가에서는 상당한 벌금이나 서비스 시장 퇴출 조치 가능
조기 점검과 접근성 선언문 작성으로 성실한 이행 의지 표명
접근성 선언문(Accessibility Statement): 서비스의 접근성 현황, 미준수 항목, 개선 계획, 연락처를 기술한 문서입니다. EAA는 이 선언문 작성도 요구하며, 완벽한 준수가 어렵더라도 성실한 이행 의지를 공식적으로 표명하는 데 중요한 역할을 합니다. W3C WAI의 공식 접근성 선언문 생성 도구를 통해 작성해볼 수 있습니다.
실무에서 가장 흔한 실수
Lighthouse 100점을 완벽한 접근성으로 착각하는 것 — 자동화 도구는 전체 이슈의 30~40%밖에 잡지 못합니다. 의미 있는 alt 텍스트, 논리적 읽기 순서, 오류 복구 경험 같은 건 도구가 판단하지 못합니다. NVDA(Windows)나 VoiceOver(macOS/iOS)로 직접 탐색해보는 테스트가 반드시 병행되어야 합니다.
outline: none을 전역으로 적용하는 것 — 미적인 이유로 포커스 링을 없애는 건 키보드 사용자에게 치명적입니다. 없애는 대신 :focus-visible로 시각적으로 더 나은 형태로 커스터마이징하는 방향이 훨씬 낫습니다.
신규 기능 배포 시 접근성 재검토를 빠뜨리는 것 — 기존 콘텐츠에 유예 기간이 있더라도 신규 기능은 즉시 적용 대상입니다. 새 컴포넌트를 배포할 때마다 접근성 회귀가 없는지 CI에서 자동으로 잡아주는 파이프라인을 갖춰두면 이 문제를 예방할 수 있습니다.
EAA 준수를 위한 실전 체크리스트 15가지
이슈 트래커나 PR 체크리스트에 바로 붙여쓸 수 있도록 정리했습니다.
폼 및 입력 요소
1. 모든 입력 필드에 <label> 또는 aria-label 연결
2. 오류 메시지를 role="alert" 또는 aria-live 영역으로 스크린 리더에 전달 (둘을 동시에 쓰지 않도록 주의)
3. 필수 입력 필드에 aria-required="true" 또는 required 명시
키보드 내비게이션
4. outline: none 전역 적용 여부 확인 및 제거
5. 모달 및 드롭다운에 포커스 트랩 구현, 닫기 시 원래 위치 복원
6. Escape 키로 오버레이/드롭다운 닫기 지원
시맨틱 마크업
7. <header>, <nav>, <main>, <footer> 등 랜드마크 요소 적용
8. 페이지당 <h1> 하나, 논리적 헤딩 계층(h1 → h2 → h3) 유지
9. <button> vs <a> 역할 구분 명확히 (동작=button, 이동=a)
10. <html lang="ko"> 언어 선언 확인
시각적 접근성
11. 일반 텍스트 색상 대비율 4.5:1 이상 확인
12. 색상 외 아이콘·텍스트 병행으로 정보 전달
13. 이미지 alt 텍스트 (의미 있는 이미지는 구체적 기술, 장식용은 alt="")
모바일 및 동작
14. 터치 타겟 최소 44×44px 확인
15. prefers-reduced-motion 미디어 쿼리 적용
마치며
처음부터 15가지를 한꺼번에 고치려 하면 시작조차 어렵습니다. 오늘 당장 할 수 있는 것부터 하나씩 확인해보는 것으로도 충분합니다. EAA 발효가 우리에게 남긴 숙제는 "완벽한 접근성"이 아니라, 접근성을 설계 초기부터 고려하는 습관을 들이는 것입니다.
지금 바로 시작해볼 수 있는 3단계:
자동화 스캔으로 현황 파악 — Chrome DevTools의 Lighthouse 탭을 열고 Accessibility 항목을 실행해볼 수 있습니다. CLI를 선호한다면 npx @axe-core/cli <URL> 명령으로 스캔을 돌려볼 수 있습니다 (기존 axe-cli는 deprecated 상태라 @axe-core/cli로 교체 필요). 현재 이슈가 몇 건인지 기준선을 파악하는 것이 출발점입니다.
컴포넌트 단위 테스트에 axe 통합 — 기존 테스트 파일에 jest-axe 또는 vitest-axe를 추가해서 Button, Modal, Form 같은 핵심 컴포넌트에 expect(await axe(container)).toHaveNoViolations() 단언문을 넣어볼 수 있습니다. 배포할 때마다 회귀를 자동으로 잡아줍니다.
키보드만으로 주요 플로우 테스트 — 마우스를 옆으로 치우고, Tab/Shift+Tab/Enter/Space/Escape 키만으로 회원가입이나 구매 같은 핵심 플로우를 처음부터 끝까지 완료해볼 수 있습니다. 막히는 지점이 바로 우선순위가 높은 수정 대상입니다.