☰ Categories

Accessibility Testing Superpower

--- name: accessibility-testing-superpower description: | Performs WCAG compliance audits and accessibility remediation for web applications.

CategoryDesign › Product design
TagsReviewingAnalyzingDeveloperChecklist
Prompt
---
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
```

What this prompt does

This prompt provides a workflow for web accessibility audits. It notes that automated scans catch only part of the issues, prioritizes semantic HTML, and uses ARIA when needed.

Model comparison

Gemini, ChatGPT, and Claude all invent a modal despite no audit target being supplied. ChatGPT is the most concise and least technically overstated.

ChatGPTTop overall
24/ 50

+ Most concise, with practical focus-management code.

- Assumes a nonexistent modal and invents an account-recovery warning.

Gemini
21/ 50

+ Clearly organizes issues and fixes by severity.

- Invents a Happynote modal and source code absent from the input.

Claude
20/ 50

+ Provides broad WCAG mapping and a verification checklist.

- Invents deletion consequences and overstates some WCAG violations.

CriterionChatGPTClaudeGeminiLeader
Instruction following222Tie
Accuracy433ChatGPT +33%
Specificity222Tie
Structure888Tie
Right length856ChatGPT +33%

Scored 1–10 by gpt-5.6-sol with model names hidden (2026-09-24). This is an AI review, not a measurement.

Read full answers

We gave three models the same input and copied their answers unedited. Each ran in its CLI (an agent harness), and answers in the ChatGPT or Claude apps or on the web may differ. Outputs are in Korean.

Input used
[Pasted material]
저희 커뮤니티 앱 '해피노트'의 회원 탈퇴 확인 모달이에요. 스크린리더·키보드 사용자한테 문제 없는지 점검해주세요.

```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>
```
gpt-5.6-luna (codex CLI · 에이전트 하네스) · Run on 2026-09-24

점검 결과, 현재 모달은 스크린리더와 키보드 사용자에게 주요 접근성 문제가 있습니다.

발견된 문제

  • 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" 영역으로 안내하면 좋습니다.

More in this category

7v7 Football Team Generator App
Accessibility Auditor
Accessibility Auditor Agent Role
Accessibility Expert
AI App Prototyping for Chat Interface