--- id: wiki-2026-0508-웹-접근성-및-성능-최적화 title: 웹 접근성 및 성능 최적화 category: 10_Wiki/Topics status: verified canonical_id: self aliases: [Web Accessibility, A11y, Performance Optimization, WCAG, Core Web Vitals] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [accessibility, a11y, performance, wcag, core-web-vitals, frontend] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: TypeScript framework: React/Web --- # 웹 접근성 및 성능 최적화 ## 매 한 줄 > **"매 a11y 의 의 minority feature 의 의 — 매 keyboard / screen reader / high contrast user 의 의 majority 의 의 better UX."**. WCAG 2.2 (2023) + WCAG 3 (draft 2026) 의 의 standard, ARIA 1.3, Core Web Vitals (LCP/INP/CLS) 의 modern user-centric performance metric. 매 2026 의 의 INP (Interaction to Next Paint, 2024 replaced FID) 의 의 main interaction metric 의 의. ## 매 핵심 ### 매 WCAG 2.2 의 4 principle (POUR) - **Perceivable**: alt text, captions, contrast 4.5:1 (AA) / 7:1 (AAA). - **Operable**: keyboard navigable, focus visible, no seizure-inducing flash. - **Understandable**: clear language, error messages, predictable. - **Robust**: valid HTML, ARIA properly used, works across AT. ### 매 Core Web Vitals (2026) - **LCP (Largest Contentful Paint)**: < 2.5s — main content visible. - **INP (Interaction to Next Paint)**: < 200ms — responsiveness (replaces FID). - **CLS (Cumulative Layout Shift)**: < 0.1 — visual stability. ### 매 응용 1. Public sector — WCAG AA 의 의 (ADA, EU EN 301 549). 2. E-commerce — checkout 의 keyboard accessibility (2024 EAA mandate). 3. SEO — Core Web Vitals 의 Google ranking factor. 4. AI tooling — voice / dictation / screen reader user 의 의 의. ## 💻 패턴 ### Semantic HTML + ARIA ```html
Submit

매 reset 의 의 의.

``` ### Focus management (modal trap) ```tsx import { useEffect, useRef } from 'react'; import FocusTrap from 'focus-trap-react'; export function Modal({ open, onClose, children }: Props) { const closeRef = useRef(null); useEffect(() => { if (open) closeRef.current?.focus(); const onKey = (e: KeyboardEvent) => e.key === 'Escape' && onClose(); window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [open, onClose]); if (!open) return null; return (
{children}
); } ``` ### LCP optimization (image priority) ```tsx // Next 15 의 의 import Image from 'next/image'; Product showcase ``` ### INP optimization (yield to main thread) ```ts // 매 long task 의 의 의 break up async function processLargeList(items: Item[]) { const CHUNK = 100; for (let i = 0; i < items.length; i += CHUNK) { items.slice(i, i + CHUNK).forEach(process); // 매 yield to browser — keep INP < 200ms await scheduler.yield?.() ?? new Promise(r => setTimeout(r, 0)); } } // React 19 useTransition function Search() { const [isPending, startTransition] = useTransition(); const [results, setResults] = useState([]); function onChange(q: string) { startTransition(() => { setResults(filter(largeList, q)); // 매 deprioritize }); } return onChange(e.target.value)} />; } ``` ### CLS prevention (reserve space) ```css /* 매 explicit aspect ratio — image / video 의 layout shift 의 */ .media { aspect-ratio: 16 / 9; width: 100%; background: #eee; } /* 매 font 의 layout shift — size-adjust + fallback metric */ @font-face { font-family: 'Inter'; src: url('/inter.woff2') format('woff2'); size-adjust: 107%; ascent-override: 90%; font-display: swap; } ``` ### Color contrast check ```ts // 매 WCAG 의 의 contrast ratio function contrastRatio(fg: string, bg: string): number { const lum = (hex: string) => { const [r, g, b] = hex.match(/\w\w/g)!.map(h => parseInt(h, 16) / 255); const lin = (c: number) => c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4; return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b); }; const [l1, l2] = [lum(fg), lum(bg)].sort((a, b) => b - a); return (l1 + 0.05) / (l2 + 0.05); // ≥ 4.5 for AA, ≥ 7 for AAA } ``` ### Automated testing (axe-core + Playwright) ```ts import { test, expect } from '@playwright/test'; import { injectAxe, checkA11y } from 'axe-playwright'; test('homepage 의 a11y', async ({ page }) => { await page.goto('/'); await injectAxe(page); await checkA11y(page, undefined, { detailedReport: true, axeOptions: { runOnly: ['wcag2a', 'wcag2aa', 'wcag22aa'] }, }); }); ``` ### Skip link + landmarks ```html
...
...
``` ## 매 결정 기준 | 상황 | Approach | |---|---| | Public sector / regulated | WCAG 2.2 AA mandatory + 3rd party audit | | E-commerce | INP optimization (form/checkout) + keyboard nav | | Marketing site | LCP < 2s + image priority + Next Image | | SPA / dashboard | Focus management + ARIA live regions | | Form-heavy | Error association (aria-describedby) + autocomplete | | Animation-heavy | prefers-reduced-motion + INP budget | **기본값**: Semantic HTML + axe-core CI + Lighthouse 의 LCP/INP/CLS budget + manual screen reader test (NVDA/VoiceOver) per release. ## 🔗 Graph - 부모: [[Frontend_Architecture]] · [[Web_Standards]] - 변형: [[WCAG_2_2]] · [[ARIA]] - 응용: [[Core-Web-Vitals]] · [[Lighthouse]] · [[Screen_Reader_Testing]] - Adjacent: [[Inclusive_Design]] · [[Universal_Design]] ## 🤖 LLM 활용 **언제**: ARIA pattern lookup, semantic HTML refactor, WCAG criteria explanation, axe rule remediation. **언제 X**: 의 actual screen reader UX evaluation (의 user testing 의 의), legal compliance ruling. ## ❌ 안티패턴 - **`
`**: keyboard / screen reader 의 의. - **Color-only signal**: red error 의 — icon / text label 의 의. - **Auto-focus on load**: 매 disorienting — modal 의 의 의 의. - **`outline: none` 의 focus state 의 X**: keyboard user 의 의 의 의. - **Lazy-load LCP image**: 매 LCP 의 의 — `loading="eager"` + `fetchpriority="high"`. - **`tabindex` 의 의 (>0)**: tab order 의 의 의. ## 🧪 검증 / 중복 - Verified (W3C WCAG 2.2 spec, web.dev Core Web Vitals, axe-core rules, MDN ARIA Authoring Practices). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — a11y + Core Web Vitals (INP) 의 의 |