Files
2nd/10_Wiki/Topics/Coding/Frontend_A11y_Testing.md
T
Antigravity Agent f8b21af4be Wiki cleanup: error-doc removal, dedup merge, link normalization
10_Wiki/Topics 대규모 정리:
- 오류 캡처/미완성 stub 문서 227개 제거
- 교차폴더 중복 43클러스터 병합 (63파일 → redirect)
- 링크명 정규화: 깨진 링크 수정·redirect 직결·개념 매핑 ~2,400건
- 카테고리 MOC 6개 신규 생성
- Graph 섹션 미해결 related-keyword 링크 10,058건 제거

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 23:52:15 +09:00

4.4 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
frontend-a11y-testing A11y Testing — axe / 키보드 / 스크린리더 Coding draft B conceptual 2026-05-09 2026-05-09
frontend
a11y
accessibility
testing
vibe-coding
language applicable_to
TS / React
Web
a11y
accessibility
axe
WCAG
ARIA
screen reader

A11y Testing

자동화는 30% — 나머지 70% 는 키보드 + 스크린리더 + 색대비. axe-core (개발) + @axe-core/playwright (E2E) 가 기본. ARIA 보다 native HTML 우선.

📖 핵심 개념

  • WCAG 2.1 AA: 일반 기준.
  • ARIA: HTML 으로 표현 안 되는 의미 추가. 그러나 native > ARIA.
  • Semantic HTML: button, nav, main, header, footer.
  • Focus management: 모달 열림 → 모달 안 / 닫힘 → 트리거.

💻 코드 패턴

axe-core dev — 자동 검사

// react app, dev only
if (process.env.NODE_ENV !== 'production') {
  import('@axe-core/react').then(({ default: axe }) => {
    axe(React, ReactDOM, 1000);
  });
}

Playwright E2E

import { expect, test } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test('home page is accessible', async ({ page }) => {
  await page.goto('/');
  const results = await new AxeBuilder({ page }).analyze();
  expect(results.violations).toEqual([]);
});

Storybook + a11y addon

// .storybook/main.ts
addons: ['@storybook/addon-a11y'],

스토리에 자동 panel — violations 보여줌.

React Testing Library + jest-axe

import { axe } from 'jest-axe';
import { render } from '@testing-library/react';

test('Button has no a11y violations', async () => {
  const { container } = render(<Button>Save</Button>);
  expect(await axe(container)).toHaveNoViolations();
});

키보드 — 모든 interactive 가능?

test('navigates with keyboard', async ({ page }) => {
  await page.goto('/');
  await page.keyboard.press('Tab');
  await expect(page.locator(':focus')).toHaveText('Skip to main content');
  await page.keyboard.press('Enter');
  await expect(page.locator('main')).toBeFocused();
});

Native HTML 우선

<!-- ❌ button 처럼 만들지 마 -->
<div onclick="..." role="button">Save</div>

<!-- ✅ -->
<button>Save</button>

Form — label 연결

<label for="email">Email</label>
<input id="email" type="email" required aria-describedby="email-error">
<p id="email-error" role="alert">잘못된 이메일</p>

모달 focus trap

import { FocusTrap } from 'focus-trap-react';

{open && (
  <FocusTrap focusTrapOptions={{ initialFocus: '#confirm', returnFocusOnDeactivate: true }}>
    <div role="dialog" aria-modal="true" aria-labelledby="t">
      <h2 id="t">정말 삭제?</h2>
      <button id="confirm">삭제</button>
      <button onClick={() => setOpen(false)}>취소</button>
    </div>
  </FocusTrap>
)}

또는 Radix UI / Headless UI — focus trap 자동.

Live region

<div role="status" aria-live="polite" className="sr-only">
  {savedAt && `Saved at ${savedAt}`}
</div>
<a href="#main" className="sr-only focus:not-sr-only">Skip to main content</a>
<main id="main">...</main>

색대비

/* WCAG AA: text 4.5:1, large text 3:1 */
.text-on-bg { color: #1a1a1a; background: #fff; } /* 16:1 */

도구: Chrome DevTools color picker, contrast-ratio.com.

🤔 의사결정 기준

검사 도구
자동 (CI) axe-playwright / axe-react
컴포넌트 단위 jest-axe + RTL
Storybook addon-a11y
키보드만 manual test
스크린리더 macOS VoiceOver / NVDA / JAWS
색대비 DevTools / Stark

안티패턴

  • <div onClick> 모든 것을: 키보드/스크린리더 없음.
  • Tabindex 양수: tab 순서 깨짐. 0 또는 -1 만.
  • outline: none: focus-visible 대체.
  • 모달 <div> body 끝에 — focus 안 trap: focus trap 라이브러리.
  • Image alt 없음: alt 빈 문자열도 의도. 의미 있는 글로.
  • placeholder 만 — label 없음: 스크린리더 사람 못 들음.
  • Color only 의미 (빨강 = 에러): 아이콘 / 텍스트 같이.

🤖 LLM 활용 힌트

  • Native HTML > ARIA.
  • axe + jest-axe + Playwright 3종.
  • Radix UI / Headless UI 가 a11y 잘 처리.

🔗 관련 문서