원클릭으로
accessibility-audit
Audit a UI or component for accessibility issues and fixes.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Audit a UI or component for accessibility issues and fixes.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Review code changes and report issues by severity with actionable fixes.
Sharpen a fuzzy intention into one measurable objective string that drives the rest of the work.
Convert a Prompt Flow PRS pipeline submission to run a Microsoft Agent Framework workflow.
Build a Model Context Protocol (MCP) server that lets an LLM call into external tools and resources.
Summarize PDF documents into concise bullet-point digests.
Bump a dependency version across a pnpm workspace and update lockfile.
| slug | accessibility-audit |
| name | Accessibility Audit |
| version | 0.2.0 |
| description | Audit a UI or component for accessibility issues and fixes. |
| category | dev-tools |
| tags | ["a11y","accessibility","wcag","frontend"] |
| inputs | [{"name":"ui_description","type":"string","required":true,"description":"Description or markup of the UI"},{"name":"wcag_level","type":"enum","required":false,"description":"Target WCAG conformance level","values":["A","AA","AAA"],"default":"AA"}] |
| output | {"format":"markdown","description":"Accessibility findings mapped to WCAG criteria with fix examples."} |
| author | badhope |
| license | MIT |
| created | "2026-06-21T00:00:00.000Z" |
| updated | "2026-06-22T00:00:00.000Z" |
Before shipping a new UI, reviewing a component library, or responding to an a11y bug.
Describe the UI and target WCAG level. Prefer providing actual HTML/JSX markup when possible—text descriptions miss subtle issues like missing labels or incorrect ARIA.
Findings mapped to WCAG criteria with severity and example fixes.
Audit the described UI for accessibility against the target WCAG level.
Check:
1. Keyboard navigation: tab order, focus indicators, trap avoidance
2. Screen readers: labels, headings, landmarks, live regions
3. Color: contrast ratios, not color-only information
4. Motion: reduced-motion support, no auto-playing content
5. Forms: labels, error association, required field indication
6. Touch/target sizes for mobile
Output:
## Findings
- **[Severity] WCAG X.X.X**: issue + fix example
## Manual Tests
List 3-5 manual checks a human should perform.
## Tools
Suggest automated tools appropriate for the UI stack.
Use this rule catalog to map findings to specific rules when auditing with axe-core.
| Rule ID | WCAG | Description |
|---|---|---|
area-alt | 1.1.1 | <area> elements must have alt text |
aria-duplicate-id | 4.1.1 | ARIA IDs must be unique |
aria-hidden-body | 4.1.2 | aria-hidden="true" must not be on <body> |
aria-required-attr | 4.1.2 | Required ARIA attributes must be present |
button-name | 4.1.2 | Buttons must have accessible names |
color-contrast | 1.4.3 | Elements must have sufficient color contrast |
form-field-multiple-labels | 1.3.1 | No more than one <label> per field |
image-alt | 1.1.1 | Images must have alt text |
input-button-name | 4.1.2 | Input buttons must have accessible names |
label | 1.3.1 | Form elements must have labels |
link-name | 4.1.2 | Links must have discernible text |
list | 1.3.1 | Lists must be properly structured (<ul>, <ol>, <li>) |
meta-viewport | 1.3.1 | Zooming must not be disabled |
object-alt | 1.1.1 | Object elements must have alt text |
td-has-data-cells | 1.3.1 | Data table cells must have headers |
th-has-data-cells | 1.3.1 | Table headers must be present |
| Rule ID | WCAG | Description |
|---|---|---|
aria-allowed-attr | 4.1.2 | ARIA attributes used incorrectly |
aria-hidden-focus | 2.1.2 | Focusable elements must not be aria-hidden |
aria-required-children | 1.3.1 | Required ARIA children missing |
aria-valid-attr-value | 4.1.2 | ARIA attributes must have valid values |
blink | 2.2.2 | <blink> elements are deprecated |
definition-list | 1.3.1 | Definition lists must use proper structure |
dlitem | 1.3.1 | Definition list items must have parent <dl> |
fieldset | 1.3.1 | Fieldset must contain legend |
flash | 2.2.2 | Flashing content must not exceed 3/sec |
frame-focusable-content | 2.1.1 | Frames must not be focusable |
html-has-lang | 3.1.1 | <html> must have lang attribute |
html-lang-valid | 3.1.1 | lang attribute must be valid IANA code |
identical-links-same-prompt | 2.4.4 | Links with same text must have same destination |
image-alt | 1.1.1 | Decorative images should have alt="" |
input-image-alt | 1.1.1 | Image inputs must have alt text |
marquee | 2.2.2 | <marquee> is deprecated and fails |
meta-refresh | 2.2.2 | Page must not auto-refresh |
nested-interactive | 2.1.1 | Interactive elements cannot nest |
no-autoplay-audio | 1.4.2 | Audio must not auto-play |
p-as-heading | 1.3.1 | Don't use <p> as headings |
scrollable-region-focusable | 2.1.2 | Scrollable regions must be keyboard accessible |
select-name | 4.1.2 | Select elements must have accessible names |
server-side-image-map | 1.1.1 | Server-side image maps not accessible |
svg-alt | 1.1.1 | SVG elements must have accessible names |
table-fake-caption | 1.3.1 | Tables with captions should use <caption> |
td-headers-attr | 1.3.1 | Header cells must use headers correctly |
valid-lang | 3.1.2 | Language attributes must be valid |
video-alt | 1.1.1 | Video elements must have captions |
Bad (React/JSX):
<input type="email" placeholder="Email address" />
<button onClick={toggle}>▼</button>
Issues:
Good:
<label htmlFor="email">Email address</label>
<input id="email" type="email" placeholder="user@example.com" aria-describedby="email-hint" />
<span id="email-hint">We'll send a confirmation link</span>
<button onClick={toggle} aria-label="Toggle menu expanded" aria-expanded={isOpen}>
<ChevronIcon />
</button>
Bad:
<div className="tabs">
<div className="tab" onClick={() => setActive(0)}>Tab 1</div>
<div className="tab" onClick={() => setActive(1)}>Tab 2</div>
</div>
Issues:
tabindex)role="tablist", role="tab", role="tabpanel"aria-selected or aria-controlsGood:
<div role="tablist" aria-label="Settings sections">
<button role="tab" aria-selected={activeTab === 0} aria-controls="panel-0"
onClick={() => setActive(0)} tabIndex={activeTab === 0 ? 0 : -1}>
Tab 1
</button>
<button role="tab" aria-selected={activeTab === 1} aria-controls="panel-1"
onClick={() => setActive(1)} tabIndex={activeTab === 1 ? 0 : -1}>
Tab 2
</button>
</div>
<div role="tabpanel" id="panel-0" aria-labelledby="tab-0">Content</div>
Bad:
function Modal({ isOpen, onClose, children }) {
if (!isOpen) return null;
return (
<div className="modal-overlay" onClick={onClose}>
<div className="modal">{children}</div>
</div>
);
}
Issues:
Good:
import { useEffect, useRef } from 'react';
function Modal({ isOpen, onClose, children }) {
const modalRef = useRef(null);
const triggerRef = useRef(null); // Pass in from parent
useEffect(() => {
if (!isOpen) return;
const previouslyFocused = document.activeElement;
const handleKeyDown = (e) => {
if (e.key === 'Escape') { onClose(); return; }
if (e.key !== 'Tab') return;
const focusable = modalRef.current.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (e.shiftKey && document.activeElement === first) {
e.preventDefault(); last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault(); first.focus();
}
};
document.addEventListener('keydown', handleKeyDown);
// Move focus into modal
modalRef.current?.focus();
return () => {
document.removeEventListener('keydown', handleKeyDown);
// Restore focus
previouslyFocused?.focus();
};
}, [isOpen, onClose]);
return (
<div className="modal-overlay" onClick={(e) => e.target === e.currentTarget && onClose()}>
<div className="modal" ref={modalRef} role="dialog" aria-modal="true" tabIndex={-1}>
{children}
</div>
</div>
);
}
Bad:
/* Gray text on white - contrast ratio ~2.5:1, fails AA */
.text-muted { color: #999; background: #fff; }
/* Light blue link on white - contrast ratio ~2.7:1, fails AA */
.link-blue { color: #5b9bd5; }
Fix - Use sufficient contrast:
/* Dark gray on white - contrast ratio 10:1 */
.text-muted { color: #595959; background: #fff; }
/* Dark blue on white - contrast ratio 7:1 */
.link-blue { color: #0049d6; }
/* Tool for checking: use axe DevTools or WebAIM contrast checker */
Minimums for WCAG AA:
Bad:
<video src="hero.mp4" autoplay muted loop></video>
<audio src="bg-music.mp3" autoplay></audio>
Issues:
Good:
<video src="hero.mp4" autoplay muted loop playsinline aria-label="Promotional video">
<track kind="captions" src="hero.vtt" srclang="en" label="English" />
</video>
<!-- OR provide user control -->
<video src="hero.mp4" controls>
<track kind="captions" src="hero.vtt" srclang="en" label="English" />
</video>
Placeholder-only labels — Placeholders disappear on focus and aren't reliable. Always use <label>.
aria-label on non-interactive elements — aria-label on <div> or <span> with no interactive role is ignored.
Focus indicators removed via outline: none — Never remove focus rings without providing alternative :focus-visible styles.
Semantic HTML ignored in favor of <div> and <span> — Native <button>, <nav>, <main>, <article> carry built-in a11y semantics.
Color as the only means of conveying information — Error states indicated only by red color fail colorblind users.
Keyboard trap in custom widgets — Custom dropdowns, modals, and sliders must trap focus correctly.
Missing alt on informative images — Decorative images should use alt="", not alt="spacer" or no alt.
Zoom disabled via maximum-scale=1.0 — Users with low vision cannot zoom, violating WCAG 1.4.4.
Dynamic content changes not announced — After login or form submission, screen readers need aria-live regions.
Assuming mobile gestures work for everyone — Touch targets need ≥44x44px (WCAG 2.5.5).
Perform these checks after automated testing passes.
aria-describedby or aria-live?prefers-reduced-motion disable animations?Input:
ui_description: 'A modal dialog with a form, opened by a button. The close button is an icon only.'
wcag_level: AA
Output:
## Findings
- **[HIGH] WCAG 4.1.2**: Icon-only close button lacks accessible name. Fix: `aria-label="Close dialog"`.
- **[MED] WCAG 2.4.3**: Focus should move to the modal heading on open; return focus to trigger on close.
- **[MED] WCAG 2.4.7**: Ensure visible focus ring on all interactive elements in the modal.
- **[MED] WCAG 1.4.11**: Close button icon may fail contrast if it's SVG with currentColor.
- **[LOW] WCAG 3.2.2**: Modal opening should not trigger context change unexpectedly.
## Manual Tests
1. Open modal with keyboard (Enter/Space on trigger). Tab through—can you reach all fields and close button?
2. Press Escape—does modal close and focus return to trigger?
3. With screen reader (NVDA/VoiceOver), enter modal—announces "dialog" role, heading, and content?
4. Check color contrast on close button icon against overlay background.
5. Zoom to 200%—can you still close the modal without horizontal scrolling?
## Tools
- axe DevTools browser extension (automated)
- WAVE API (visual contrast check)
- NVDA + Firefox (screen reader testing on Windows)
- Accessibility Insights (guided tests)