| name | accessibility-wcag |
| description | Enforce WCAG 2.2 accessibility standards. Use when creating UI components, reviewing frontend code, or when accessibility issues are detected. Covers semantic HTML, ARIA, keyboard navigation, and color contrast. |
| allowed-tools | Read, Glob, Grep, Edit, Write, Bash |
| license | MIT |
| metadata | {"author":"antigravity-team","version":"1.0"} |
Accessibility (WCAG 2.2)
์น ์ ๊ทผ์ฑ ํ์ค WCAG 2.2๋ฅผ ์ค์ํ๋๋ก ๊ฐ์ ํ๋ ์คํฌ์
๋๋ค.
2025 Context
WCAG 2.2๋ 2023๋
10์ ISO ํ์ค(ISO/IEC 40500)์ผ๋ก ์ฑํ๋์์ต๋๋ค.
์ ๋ฝ ์ ๊ทผ์ฑ๋ฒ(EAA)์ 2025๋
6์๋ถํฐ ์ํ๋ฉ๋๋ค.
Core Principles (POUR)
| ์์น | ์ค๋ช
| ์์ |
|---|
| Perceivable | ์ธ์ง ๊ฐ๋ฅ | ๋์ฒด ํ
์คํธ, ์๋ง, ์์ ๋๋น |
| Operable | ์กฐ์ ๊ฐ๋ฅ | ํค๋ณด๋ ์ ๊ทผ, ์ถฉ๋ถํ ์๊ฐ |
| Understandable | ์ดํด ๊ฐ๋ฅ | ๋ช
ํํ ์ธ์ด, ์์ธก ๊ฐ๋ฅํ ๋์ |
| Robust | ๊ฒฌ๊ณ ํจ | ๋ณด์กฐ ๊ธฐ์ ํธํ์ฑ |
Rules
1. Semantic HTML (ํ์)
<div onClick={handleClick}>๋ฒํผ</div>
<div class="header">์ ๋ชฉ</div>
<button onClick={handleClick}>๋ฒํผ</button>
<h1>์ ๋ชฉ</h1>
2. ์ด๋ฏธ์ง ๋์ฒด ํ
์คํธ (ํ์)
<img src="logo.png" />
<img src="chart.png" alt="์ด๋ฏธ์ง" />
<img src="logo.png" alt="ํ์ฌ๋ช
๋ก๊ณ " />
<img src="chart.png" alt="2024๋
๋งค์ถ ์ฆ๊ฐ ์ถ์ด ๊ทธ๋ํ" />
<img src="decoration.png" alt="" role="presentation" />
3. ํค๋ณด๋ ์ ๊ทผ์ฑ (ํ์)
<div onClick={handleClick} style={{ cursor: 'pointer' }}>
ํด๋ฆญ
</div>
<button onClick={handleClick}>ํด๋ฆญ</button>
<div
role="button"
tabIndex={0}
onClick={handleClick}
onKeyDown={(e) => e.key === 'Enter' && handleClick()}
>
ํด๋ฆญ
</div>
4. ํฌ์ปค์ค ๊ด๋ฆฌ
button:focus {
outline: none;
}
button:focus {
outline: 2px solid #005fcc;
outline-offset: 2px;
}
button:focus-visible {
outline: 2px solid #005fcc;
}
5. ์์ ๋๋น (WCAG AA ๊ธฐ์ค)
| ํ
์คํธ ํฌ๊ธฐ | ์ต์ ๋๋น์จ |
|---|
| ์ผ๋ฐ ํ
์คํธ | 4.5:1 |
| ํฐ ํ
์คํธ (18pt+, 14pt bold+) | 3:1 |
| UI ์ปดํฌ๋ํธ/๊ทธ๋ํฝ | 3:1 |
.text {
color: #999;
background: #fff;
}
.text {
color: #595959;
background: #fff;
}
6. ํผ ๋ ์ด๋ธ (ํ์)
<input type="email" placeholder="์ด๋ฉ์ผ" />
<label htmlFor="email">์ด๋ฉ์ผ</label>
<input id="email" type="email" />
<input type="email" aria-label="์ด๋ฉ์ผ ์ฃผ์" placeholder="์ด๋ฉ์ผ" />
7. ARIA ์ญํ ๋ฐ ์์ฑ
<div
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
>
<h2 id="modal-title">ํ์ธ</h2>
...
</div>
<div role="alert" aria-live="polite">
์ ์ฅ๋์์ต๋๋ค.
</div>
<button aria-busy={isLoading} disabled={isLoading}>
{isLoading ? '์ฒ๋ฆฌ ์ค...' : '์ ์ถ'}
</button>
8. ๊ฑด๋๋ฐ๊ธฐ ๋งํฌ
<a href="#main-content" className="skip-link">
๋ณธ๋ฌธ์ผ๋ก ๊ฑด๋๋ฐ๊ธฐ
</a>
.skip-link {
position: absolute;
top: -40px;
left: 0;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
WCAG 2.2 ์ ๊ท ๊ธฐ์ค
2.4.11 Focus Not Obscured (AA)
.header { position: fixed; top: 0; }
:target {
scroll-margin-top: 80px;
}
*:focus {
scroll-margin-top: 80px;
}
2.5.7 Dragging Movements (AA)
<DraggableList onDrag={handleReorder} />
<DraggableList onDrag={handleReorder}>
<button onClick={moveUp}>์๋ก ์ด๋</button>
<button onClick={moveDown}>์๋๋ก ์ด๋</button>
</DraggableList>
2.5.8 Target Size (AA)
button, a, input[type="checkbox"] {
min-width: 44px;
min-height: 44px;
}
ํ
์คํธ ๋๊ตฌ
์๋ํ ๋๊ตฌ
npm install @axe-core/react
npm install eslint-plugin-jsx-a11y --save-dev
npm install -g @lhci/cli
lhci autorun
eslint ์ค์
{
"extends": ["plugin:jsx-a11y/recommended"],
"rules": {
"jsx-a11y/alt-text": "error",
"jsx-a11y/anchor-is-valid": "error",
"jsx-a11y/click-events-have-key-events": "error",
"jsx-a11y/no-static-element-interactions": "error"
}
}
์๋ ํ
์คํธ ์ฒดํฌ๋ฆฌ์คํธ
Workflow
1. ์ปดํฌ๋ํธ ์์ฑ ์
์ฒดํฌํฌ์ธํธ:
1. ์๋งจํฑ HTML ์ฌ์ฉํ๋๊ฐ?
2. ํค๋ณด๋ ์ ๊ทผ ๊ฐ๋ฅํ๊ฐ?
3. ์ ์ ํ ARIA ์์ฑ์ด ์๋๊ฐ?
4. ํฌ์ปค์ค ์คํ์ผ์ด ์๋๊ฐ?
2. ์ฝ๋ ๋ฆฌ๋ทฐ ์
์ ๊ทผ์ฑ ์ฒดํฌ:
1. img์ alt ์๋๊ฐ?
2. form์ label ์๋๊ฐ?
3. ์์ ๋๋น ์ถฉ๋ถํ๊ฐ?
4. ํฐ์น ํ๊ฒ ํฌ๊ธฐ ์ถฉ๋ถํ๊ฐ?
Checklist
References