+ 세 답 중 가장 간결하고 포커스 처리도 실용적이다.
- 없는 모달을 전제하고 계정 복구 불가 문구도 창작했다.
대상 페이지나 컴포넌트와 WCAG 기준을 넣으면 자동 검사, 키보드 탐색, 스크린 리더 확인, 개선 로드맵을 안내합니다.
| 분류 | 디자인 › 화면·제품 설계 |
|---|---|
| 태그 | 검토분석개발자체크리스트 |
---
name: accessibility-testing-superpower
description: |
Performs WCAG compliance audits and accessibility remediation for web applications.
Use when: 1) Auditing UI for WCAG 2.1/2.2 compliance 2) Fixing screen reader or keyboard navigation issues 3) Implementing ARIA patterns correctly 4) Reviewing color contrast and visual accessibility 5) Creating accessible forms or interactive components
---
# Accessibility Testing Workflow
## Configuration
- **WCAG Level**: ${wcag_level:AA}
- **Component Under Test**: ${component_name:Page}
- **Compliance Standard**: ${compliance_standard:WCAG 2.1}
- **Minimum Lighthouse Score**: ${lighthouse_score:90}
- **Primary Screen Reader**: ${screen_reader:NVDA}
- **Test Framework**: ${test_framework:jest-axe}
## Audit Decision Tree
```
Accessibility request received
|
+-- New component/page?
| +-- Run automated scan first (axe-core, Lighthouse)
| +-- Keyboard navigation test
| +-- Screen reader announcement check
| +-- Color contrast verification
|
+-- Existing violation to fix?
| +-- Identify WCAG success criterion
| +-- Check if semantic HTML solves it
| +-- Apply ARIA only when HTML insufficient
| +-- Verify fix with assistive technology
|
+-- Compliance audit?
+-- Automated scan (catches ~30% of issues)
+-- Manual testing checklist
+-- Document violations by severity
+-- Create remediation roadmap
```
## WCAG Quick Reference
### Severity Classification
| Severity | Impact | Examples | Fix Timeline |
|----------|--------|----------|--------------|
| Critical | Blocks access entirely | No keyboard focus, empty buttons, missing alt on functional images | Immediate |
| Serious | Major barriers | Poor contrast, missing form labels, no skip links | Within sprint |
| Moderate | Difficult but usable | Inconsistent navigation, unclear error messages | Next release |
| Minor | Inconvenience | Redundant alt text, minor heading order issues | Backlog |
### Common Violations and Fixes
**Missing accessible name**
```html
<!-- Violation -->
<button><svg>...</svg></button>
<!-- Fix: aria-label -->
<button aria-label="Close dialog"><svg>...</svg></button>
<!-- Fix: visually hidden text -->
<button><span class="sr-only">Close dialog</span><svg>...</svg></button>
```
**Form label association**
```html
<!-- Violation -->
<label>Email</label>
<input type="email">
<!-- Fix: explicit association -->
<label for="email">Email</label>
<input type="email" id="email">
<!-- Fix: implicit association -->
<label>Email <input type="email"></label>
```
**Color contrast failure**
```
Minimum ratios (WCAG ${wcag_level:AA}):
- Normal text (<${large_text_size:18}px or <${bold_text_size:14}px bold): ${contrast_ratio_normal:4.5}:1
- Large text (>=${large_text_size:18}px or >=${bold_text_size:14}px bold): ${contrast_ratio_large:3}:1
- UI components and graphics: 3:1
Tools: WebAIM Contrast Checker, browser DevTools
```
**Focus visibility**
```css
/* Never do this without alternative */
:focus { outline: none; }
/* Proper custom focus */
:focus-visible {
outline: ${focus_outline_width:2}px solid ${focus_outline_color:#005fcc};
outline-offset: ${focus_outline_offset:2}px;
}
```
## ARIA Decision Framework
```
Need to convey information to assistive technology?
|
+-- Can semantic HTML do it?
| +-- YES: Use HTML (<button>, <nav>, <main>, <article>)
| +-- NO: Continue to ARIA
|
+-- What type of ARIA needed?
+-- Role: What IS this element? (role="dialog", role="tab")
+-- State: What condition? (aria-expanded, aria-checked)
+-- Property: What relationship? (aria-labelledby, aria-describedby)
+-- Live region: Dynamic content? (aria-live="${aria_live_mode:polite}")
```
### ARIA Patterns for Common Widgets
**Disclosure (show/hide)**
```html
<button aria-expanded="false" aria-controls="content-1">
Show details
</button>
<div id="content-1" hidden>
Content here
</div>
```
**Tab interface**
```html
<div role="tablist" aria-label="${component_name:Settings}">
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">
General
</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2" tabindex="-1">
Privacy
</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">...</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>...</div>
```
**Modal dialog**
```html
<div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
<h2 id="dialog-title">Confirm action</h2>
<p>Are you sure you want to proceed?</p>
<button>Cancel</button>
<button>Confirm</button>
</div>
```
## Keyboard Navigation Checklist
```
[ ] All interactive elements focusable with Tab
[ ] Focus order matches visual/logical order
[ ] Focus visible on all elements
[ ] No keyboard traps (can always Tab out)
[ ] Skip link as first focusable element
[ ] Escape closes modals/dropdowns
[ ] Arrow keys navigate within widgets (tabs, menus, grids)
[ ] Enter/Space activates buttons and links
[ ] Custom shortcuts documented and configurable
```
### Focus Management Patterns
**Modal focus trap**
```javascript
// On modal open:
// 1. Save previously focused element
// 2. Move focus to first focusable in modal
// 3. Trap Tab within modal boundaries
// On modal close:
// 1. Return focus to saved element
```
**Dynamic content**
```javascript
// After adding content:
// - Announce via aria-live region, OR
// - Move focus to new content heading
// After removing content:
// - Move focus to logical next element
// - Never leave focus on removed element
```
## Screen Reader Testing
### Announcement Verification
| Element | Should Announce |
|---------|-----------------|
| Button | Role + name + state ("Submit button") |
| Link | Name + "link" ("Home page link") |
| Image | Alt text OR "decorative" (skip) |
| Heading | Level + text ("Heading level 2, About us") |
| Form field | Label + type + state + instructions |
| Error | Error message + field association |
### Testing Commands (Quick Reference)
**VoiceOver (macOS)**
- VO = Ctrl + Option
- VO + A: Read all
- VO + Right/Left: Navigate elements
- VO + Cmd + H: Next heading
- VO + Cmd + J: Next form control
**${screen_reader:NVDA} (Windows)**
- NVDA + Down: Read all
- Tab: Next focusable
- H: Next heading
- F: Next form field
- B: Next button
## Automated Testing Integration
### axe-core in tests
```javascript
// ${test_framework:jest-axe}
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
test('${component_name:component} is accessible', async () => {
const { container } = render(<${component_name:MyComponent} />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
```
### Lighthouse CI threshold
```javascript
// lighthouserc.js
module.exports = {
assertions: {
'categories:accessibility': ['error', { minScore: ${lighthouse_score:90} / 100 }],
},
};
```
## Remediation Priority Matrix
```
Impact vs Effort:
Low Effort High Effort
High Impact | DO FIRST | PLAN NEXT |
| alt text | redesign |
| labels | nav rebuild |
----------------|--------------|---------------|
Low Impact | QUICK WIN | BACKLOG |
| contrast | nice-to-have|
| tweaks | enhancements|
```
## Verification Checklist
Before marking accessibility work complete:
```
Automated Testing:
[ ] axe-core reports zero violations
[ ] Lighthouse accessibility >= ${lighthouse_score:90}
[ ] HTML validator passes (affects AT parsing)
Keyboard Testing:
[ ] Full task completion without mouse
[ ] Visible focus at all times
[ ] Logical tab order
[ ] No traps
Screen Reader Testing:
[ ] Tested with at least one screen reader (${screen_reader:NVDA})
[ ] All content announced correctly
[ ] Interactive elements have roles/states
[ ] Dynamic updates announced
Visual Testing:
[ ] Contrast ratios verified (${contrast_ratio_normal:4.5}:1 minimum)
[ ] Works at ${zoom_level:200}% zoom
[ ] No information conveyed by color alone
[ ] Respects prefers-reduced-motion
```웹 애플리케이션 접근성 감사를 위한 워크플로다. 자동 검사는 일부 문제만 잡는다고 명시하고, 의미론적 HTML을 우선하며 ARIA는 필요할 때 쓰도록 한다.
Gemini ChatGPT Claude 모두 실제 감사 대상 없이 모달을 상상해 답한 것이 핵심 결함이다. 그중 ChatGPT가 가장 간결하고 기술적 과장이 적다.
+ 세 답 중 가장 간결하고 포커스 처리도 실용적이다.
- 없는 모달을 전제하고 계정 복구 불가 문구도 창작했다.
+ 문제와 수정안을 심각도별로 명료하게 구성했다.
- 입력에 없는 해피노트 모달과 원본 코드를 지어냈다.
+ WCAG 기준과 검증 체크리스트를 폭넓게 제시했다.
- 삭제 결과까지 창작하고 일부 WCAG 판정도 과도하다.
| 기준 | ChatGPT | Claude | Gemini | 앞선 모델 |
|---|---|---|---|---|
| 지시 준수 | 2 | 2 | 2 | 동점 |
| 정확성 | 4 | 3 | 3 | ChatGPT +33% |
| 구체성 | 2 | 2 | 2 | 동점 |
| 구성·가독성 | 8 | 8 | 8 | 동점 |
| 분량 적정성 | 8 | 5 | 6 | ChatGPT +33% |
gpt-5.6-sol가 모델 이름을 가린 채 1~10점으로 채점했습니다 (2026-09-24). 측정값이 아니라 AI 심사 결과입니다.
같은 입력을 세 모델에 넣어 받은 답을 그대로 옮겼습니다. 각 CLI(에이전트 하네스)에서 실행했습니다. ChatGPT·Claude 앱이나 웹에서는 답이 다를 수 있습니다.
[붙여 넣은 자료]
저희 커뮤니티 앱 '해피노트'의 회원 탈퇴 확인 모달이에요. 스크린리더·키보드 사용자한테 문제 없는지 점검해주세요.
```html
<div class="modal" style="display:block;">
<div class="modal-content">
<span class="close" onclick="closeModal()">×</span>
<h3>회원 탈퇴 확인</h3>
<p>정말 탈퇴하시겠습니까?</p>
<button style="outline:none;">확인</button>
<button style="outline:none;">취소</button>
</div>
</div>
```점검 결과, 현재 모달은 스크린리더와 키보드 사용자에게 주요 접근성 문제가 있습니다.
role="dialog"와 aria-modal="true"가 없음span은 키보드로 접근하거나 실행할 수 없음onclick만 사용하면 키보드 조작이 보장되지 않음outline: none으로 포커스 표시를 제거함Tab 키가 모달 밖으로 빠져나갈 수 있음Esc로 닫히는 동작이 없음<div
id="withdraw-modal"
class="modal"
role="dialog"
aria-modal="true"
aria-labelledby="withdraw-title"
aria-describedby="withdraw-description"
hidden
>
<div class="modal-content">
<button
type="button"
class="close"
aria-label="회원 탈퇴 확인 창 닫기"
>
<span aria-hidden="true">×</span>
</button>
<h2 id="withdraw-title">회원 탈퇴 확인</h2>
<p id="withdraw-description">
정말 탈퇴하시겠습니까? 탈퇴 후에는 계정을 복구할 수 없습니다.
</p>
<div class="modal-actions">
<button type="button" class="confirm-withdraw">
확인
</button>
<button type="button" class="cancel-withdraw">
취소
</button>
</div>
</div>
</div>
const modal = document.querySelector('#withdraw-modal');
const closeButton = modal.querySelector('.close');
const cancelButton = modal.querySelector('.cancel-withdraw');
let previouslyFocusedElement;
function openModal() {
previouslyFocusedElement = document.activeElement;
modal.hidden = false;
closeButton.focus();
}
function closeModal() {
modal.hidden = true;
previouslyFocusedElement?.focus();
}
modal.addEventListener('click', (event) => {
if (
event.target.closest('.close') ||
event.target.closest('.cancel-withdraw')
) {
closeModal();
}
});
modal.addEventListener('keydown', (event) => {
if (event.key === 'Escape') {
closeModal();
return;
}
if (event.key !== 'Tab') return;
const focusable = modal.querySelectorAll(
'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled])'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (event.shiftKey && document.activeElement === first) {
event.preventDefault();
last.focus();
} else if (!event.shiftKey && document.activeElement === last) {
event.preventDefault();
first.focus();
}
});
button:focus-visible {
outline: 3px solid #005fcc;
outline-offset: 3px;
}
추가로 모달이 열렸을 때 배경 콘텐츠에는 inert를 적용해 키보드와 스크린리더가 접근하지 못하게 해야 합니다. 또한 실제 탈퇴 처리 결과나 오류 메시지는 aria-live="polite" 영역으로 안내하면 좋습니다.
| 7대7 축구 팀 앱 설계 프롬프트 | |
| 접근성 감사자 역할을 맡기는 프롬프트 | |
| 접근성 감사자 역할 프롬프트 | |
| 접근성 전문가 역할 프롬프트 | |
| Android 채팅 앱 원형 프롬프트 |