프런트엔드 접근성은(는) 무엇을 하나요?
접근성은 모든 UI 변경의 일부입니다 — 함께 설계, 구축, 테스트됩니다. UI 작업은 키보드만으로 작동하고, 모든 컨트롤이 이름, 역할, 상태를 알리며, 자동 검사가 통과되고, 테스트가 역할 기반으로 쿼리할 때까지 완료된 것으로 간주하지 않습니다.
목표: WCAG 2.2 레벨 AA — EU에서는 유럽 접근성법(국가별 법률(예: BFSG, EN 301 549))에 따라 많은 제품에 의무화되어 있습니다. 발견 사항은 WCAG A/AA 기준 또는 보조 기술 사용자가 완료할 수 없는 구체적인 작업을 인용합니다. AAA 기준 및 관례(예: <h1> 하나, 44px 대상)는 권장 사항이며, 차단 요소가 아닙니다.
| 작업 | 참고 자료 |
|---|---|
| 요소, 랜드마크, 제목, ARIA, 접근 가능한 이름, 비활성화 상태 선택 | references/semantics-and-aria.md |
| 포커스 링, 탭 순서, 건너뛰기 링크, 대화상자, 경로 변경, 위젯 키 맵 | references/focus-and-keyboard.md |
| 레이블, 자동 완성, 입력 유형, 유효성 검사 오류, 제출 동작 | references/forms.md |
| 변경 알림, 라이브 리전, 토스트, 대체 텍스트, SVG, 미디어 | references/screen-readers.md |
| 대비, 색상, 대상 크기, 동작, 확대/축소, 리플로우, 콘텐츠 처리 | references/visual-and-motion.md |
| 자동 및 수동 테스트, CI | references/testing.md |
| 기존 UI 감사/검토, 보고서 형식 | references/audit.md |
| WCAG 2.2 기준 조회 | references/wcag.md |
핵심 규칙 — 항상 적용
네이티브 우선. 동작에는 <button>, 탐색에는 <a href> / 라우터 <Link>, <label>, <fieldset>, <table>, <dialog>, 랜드마크를 사용합니다. <div onClick>은 절대 사용하지 않습니다. ARIA가 없는 것이 잘못된 ARIA보다 낫습니다. 역할은 전체 키보드 모델을 약속합니다. 복잡한 위젯(콤보박스, 메뉴, 날짜 선택기)은 프로젝트의 접근 가능한 헤드리스 라이브러리에서 가져오며, 직접 구축하지 않습니다.
모든 대화형 요소에는 접근 가능한 이름이 있습니다 [4.1.2]. 아이콘 전용 버튼은 aria-label을 받고, 아이콘은 aria-hidden="true"로 설정합니다. 보이는 레이블 텍스트는 접근 가능한 이름의 일부입니다 [2.5.3]. 링크는 이동 위치를 명시합니다 — "여기를 클릭" 금지 [2.4.4].
키보드 [2.1.1, 2.1.2]. 포인터로 작동하는 모든 것은 키보드로도 작동합니다. 트랩이 없어야 합니다. Tab 순서 = 시각적 순서; 양수 tabIndex는 절대 사용하지 않습니다. Escape는 최상위 오버레이를 닫습니다. 드래그 및 제스처에는 단일 포인터 대안이 있습니다 [2.5.7, 2.5.1].
보이는 포커스 [2.4.7, 1.4.11]. :focus-visible 스타일을 지정합니다. 검증된 대체 스타일(인접한 모든 색상 대비 ≥ 3:1) 없이 outline: none을 사용하지 않습니다. 포커스가 고정 헤더/배너 뒤에 숨겨지지 않도록 합니다 [2.4.11] — scroll-padding-top을 사용합니다.
포커스 관리. 대화상자: 열리면 포커스가 내부로 이동하고, 트랩되며, 배경은 비활성화되고, 닫히면 트리거로 포커스가 돌아갑니다. SPA 경로 변경: document.title을 업데이트하고 [2.4.2] 포커스를 새 <h1>(tabIndex={-1}) 또는 <main>으로 이동합니다.
폼 [1.3.1, 3.3.1, 3.3.2]. 모든 컨트롤에 보이는 <label>이 있어야 합니다 — 플레이스홀더는 레이블이 아닙니다. 올바른 type, inputMode, autoComplete을 사용합니다 [1.3.5]. 오류는 인라인으로 표시하고, aria-describedby로 연결하며, 필드는 aria-invalid="true"로 설정하고, 제출 시 첫 번째 잘못된 필드에 포커스를 맞춥니다. 제출 시 유효성을 검사합니다. 유효할 때까지 제출을 비활성화하지 마십시오. 붙여넣기를 차단하지 마십시오 [3.3.8].
동적 변경 알림 [4.1.3]. 필드별 → aria-describedby; 긴급하지 않음(저장됨, 결과 수, 로딩 중) → role="status"; 긴급한 양식 수준 오류 → role="alert". 라이브 리전은 텍스트가 변경되기 전에 DOM에 존재해야 합니다.
색상 및 대비 [1.4.3, 1.4.11, 1.4.1]. 텍스트 ≥ 4.5:1(큰 텍스트 ≥ 3:1), UI 경계, 아이콘 및 포커스 링 ≥ 3:1 — 디자인 토큰을 통해 시행됩니다. 색상이 유일한 단서가 되어서는 안 됩니다.
대상 [2.5.8]. ≥ 24×24 CSS px 또는 충분한 간격; 데스크톱 40 px / 터치 44 px를 목표로 합니다. 장식용 오버레이는 pointer-events: none을 설정합니다.
동작 및 시간 [2.3.3, 2.2.2]. 애니메이션은 @media (prefers-reduced-motion: no-preference)를 통해 옵트인합니다. 축소된 동작 설정에서는 시차/자동 재생을 비활성화합니다. 5초 이상 움직이는 것은 일시 중지할 수 있어야 합니다. 동작이나 오류가 있는 토스트는 해제될 때까지 유지됩니다.
확대/축소 및 리플로우 [1.4.4, 1.4.10]. 200% 텍스트 크기 및 320 CSS px 너비에서 가로 스크롤 없이 작동해야 합니다. maximum-scale=1 / user-scalable=no를 절대 사용하지 마십시오.
페이지 기본 사항. <html lang>이 설정되고 UI 언어로 업데이트되어야 합니다 [3.1.1]; 하나의 보이는 <main>; 제목이 개요를 형성합니다. 탐색이 <main> 앞에 오는 경우 건너뛰기 링크가 첫 번째여야 합니다 [2.4.1].
이미지. 의미 있는 이미지 → alt는 목적을 설명합니다. 장식용 → alt=""; 기능적(이미지가 링크/버튼인 경우) → 동작을 설명합니다 [1.1.1].
테스트 의무
모든 UI 변경 사항에는 다음이 포함됩니다.
- 역할과 이름으로 쿼리하고(
getByRole) 키보드 경로를 다루는(user.tab(),{Escape},toHaveFocus()) 컴포넌트 테스트, - addon-a11y 검사가 오류로 실행되는 Storybook 스토리(CI 실패),
- 중요한 페이지 및 상태에서 Playwright +
@axe-core/playwright스캔(WCAG 2.x A/AA 태그), - 흐름의 수동 키보드 전용 통과; 중요한 흐름의 스크린 리더 통과(VoiceOver/NVDA).
자동화된 도구는 문제의 일부만 찾습니다. axe 실행이 깨끗하다고 해서 WCAG를 준수하는 것은 아닙니다. 자세한 내용: references/testing.md.
완료 전 — 변경 사항 스캔
| 패턴 | 수정 |
|---|---|
<div onClick / <span onClick | <button> 또는 탐색 시 <a href> |
키 처리 없는 role="button"/role="tab" | 네이티브 요소 또는 APG 키 맵 |
focus-visible 대체 없이 outline: none / outline-none | 브라우저 링 유지 또는 검증된 사용자 정의 링 유지 |
양수 tabIndex | DOM 순서 수정, 0 / -1 사용 |
alt 없는 <img | alt="" 또는 목적 설명 |
| 이름 없는 아이콘 버튼 | aria-label, 아이콘 aria-hidden |
<label>이 없거나 플레이스홀더를 레이블로 사용하는 입력 | 보이는 연결된 레이블 |
제출 시 disabled={!isValid} | 활성 상태 유지, 제출 시 유효성 검사 |
{msg && <div role="status">…} | 빈 영역을 먼저 렌더링하고 텍스트 업데이트 |
성공 메시지에 aria-live="assertive" | role="status" |
포커스 가능한 요소 위나 그 요소에 aria-hidden | 제거하거나 하위 트리를 inert로 설정 |
onPaste + preventDefault | 붙여넣기 허용 |
maximum-scale=1 / user-scalable=no | 제거 |
scroll-padding-top 없는 고정 헤더 | 스크롤러에 패딩 추가 |
| 버튼 대안 없는 드래그/제스처 | 단일 포인터 경로 추가 |
transition: all, 축소된 동작 보호 없는 모션 | 속성 나열, no-preference로 래핑 |
보고
감사 및 검토는 대화 언어로 보고되며, 각 근본 원인에 대해 모든 위치, WCAG 기준, 사용자 영향 및 구체적인 수정 사항을 포함합니다 — 형식은 references/audit.md에 있습니다. 심각도는 코드 검토 스킬에 매핑됩니다: HIGH → Blocker/Major, MEDIUM → Major/Minor, LOW → Minor.
출처
다음에서 결합됨: Addy Osmani의 web-quality-skills accessibility (MIT), Jakub Krehel의 better-accessibility (MIT), ibelick의 fixing-accessibility (MIT), Vercel의 Web Interface Guidelines (MIT); W3C WCAG 2.2 및 WAI-ARIA Authoring Practices.