Files
2nd/10_Wiki/Topics/Frontend/Accessibility (A11y).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

5.3 KiB

id, title, category, status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, verification_status, tags, raw_sources, last_reinforced, github_commit, tech_stack
id title category status canonical_id aliases duplicate_of source_trust_level confidence_score verification_status tags raw_sources last_reinforced github_commit tech_stack
wiki-2026-0508-accessibility-a11y Accessibility (A11y) 10_Wiki/Topics verified self
A11y
Web Accessibility
WCAG
none A 0.9 applied
accessibility
a11y
wcag
aria
frontend
2026-05-10 pending
language framework
HTML/CSS/JS WCAG 2.2

Accessibility (A11y)

매 한 줄

"매 사용자가 매 콘텐츠에 매 접근 가능". A11y는 visual/auditory/motor/cognitive 장애 사용자도 web product를 사용할 수 있도록 design + implement 하는 practice. 2026 기준 WCAG 2.2가 standard, EU EAA 강제 발효(2025-06)로 commercial site 의 legal requirement.

매 핵심

매 4 원칙 (POUR)

  • Perceivable: 매 contrast, alt text, captions — 매 sense 통해 perceive 가능.
  • Operable: keyboard nav, focus management, no seizure-triggering content.
  • Understandable: clear language, predictable behavior, input help.
  • Robust: valid semantic HTML, ARIA correct, assistive tech compatible.

매 ARIA vs Semantic HTML

  • First rule: 매 native HTML element 가 있으면 ARIA 의 X. <button> > <div role="button">.
  • ARIA 사용 case: dynamic widget (combobox, tabpanel, dialog), live region, no native equivalent.

매 응용

  1. WCAG 2.2 AA conformance — most legal threshold.
  2. Screen reader testing (VoiceOver/NVDA/JAWS).
  3. Keyboard-only navigation flow.

💻 패턴

<a href="#main" class="skip-link">Skip to main content</a>
<style>
.skip-link {
  position: absolute;
  left: -9999px;
}
.skip-link:focus {
  left: 0; top: 0;
  background: #000; color: #fff;
  padding: 0.5rem 1rem;
  z-index: 100;
}
</style>
<main id="main" tabindex="-1">...</main>

Accessible modal (focus trap)

import { useEffect, useRef } from 'react';

function Modal({ isOpen, onClose, children }) {
  const ref = useRef<HTMLDivElement>(null);
  const lastFocus = useRef<HTMLElement | null>(null);

  useEffect(() => {
    if (!isOpen) return;
    lastFocus.current = document.activeElement as HTMLElement;
    ref.current?.focus();

    const onKey = (e: KeyboardEvent) => {
      if (e.key === 'Escape') onClose();
    };
    document.addEventListener('keydown', onKey);
    return () => {
      document.removeEventListener('keydown', onKey);
      lastFocus.current?.focus();
    };
  }, [isOpen]);

  if (!isOpen) return null;
  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-labelledby="modal-title"
      ref={ref}
      tabIndex={-1}
    >
      <h2 id="modal-title">Confirm</h2>
      {children}
    </div>
  );
}

Live region for async updates

<div aria-live="polite" aria-atomic="true" id="status"></div>
<script>
  // 매 새로운 toast 매 polite 알림
  document.getElementById('status').textContent = 'Saved successfully';
</script>

Form validation with aria-describedby

<label for="email">Email</label>
<input
  id="email"
  type="email"
  aria-invalid="true"
  aria-describedby="email-err"
  required
/>
<span id="email-err" role="alert">유효한 이메일 입력</span>

Visually hidden but screen-reader visible

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}

Color contrast check (WCAG AA = 4.5:1 for body)

function relLuminance(rgb: [number, number, number]) {
  const [r, g, b] = rgb.map(v => {
    v /= 255;
    return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
  });
  return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
function contrast(a: [number,number,number], b: [number,number,number]) {
  const [L1, L2] = [relLuminance(a), relLuminance(b)].sort((x,y) => y-x);
  return (L1 + 0.05) / (L2 + 0.05);
}

Reduced motion

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

매 결정 기준

상황 Approach
Custom widget ARIA + keyboard handler
Native equivalent 존재 Use semantic HTML, no ARIA
Async status aria-live="polite"
Critical alert role="alert" (assertive)
Modal focus trap + aria-modal="true"

기본값: semantic HTML first, ARIA only as supplement.

🔗 Graph

🤖 LLM 활용

언제: ARIA pattern lookup, WCAG criterion explanation, accessibility audit script generation. 언제 X: real screen reader testing — manual + actual AT 사용 필수.

안티패턴

  • div soup: 매 <div onclick> — keyboard 의 X.
  • alt="image": meaningless alt — describe content or alt="" for decorative.
  • Removed focus outline: outline:none without replacement — keyboard user 의 lost.
  • Color-only signal: error 만 red — 매 color blind user invisible.
  • ARIA overuse: role="button" on <button> — redundant + harmful.

🧪 검증 / 중복

  • Verified (WCAG 2.2 W3C Recommendation 2023, ARIA 1.2 spec).
  • 신뢰도 A.

🕓 Changelog

날짜 변경
2026-05-08 Phase 1
2026-05-10 Manual cleanup — A11y 4 원칙 + ARIA pattern 정리