Accessibility Audit Skill
When to Use This Skill
- Auditing React components for WCAG 2.1 compliance
- Adding ARIA labels, roles, and descriptions
- Fixing keyboard navigation issues
- Making components screen-reader friendly
- Before shipping any public-facing UI
- Code review for accessibility issues
The Core Principle
Every interactive element must be:
- Perceivable — visible or audible to all users
- Operable — usable with keyboard alone
- Understandable — clear labels and error messages
- Robust — works with assistive technologies
Rule 1 — Images Must Have Alt Text
// ❌ Missing alt text — screen readers say nothing
<img src="/avatar.png" />
// ✅ Descriptive alt text
<img src="/avatar.png" alt="Profile photo of Kirti Kaushal" />
// ✅ Decorative image — empty alt tells screen reader to skip
<img src="/divider.png" alt="" role="presentation" />
AI instruction: When generating image elements, ALWAYS include alt prop. If decorative, use alt="".
Rule 2 — Buttons Must Have Labels
// ❌ Icon-only button — screen reader says "button"
<button /></button>
// ✅ With aria-label
<button aria-label="Close dialog"><CloseIcon /></button>
// ✅ With visually hidden text
<button
<CloseIcon aria-hidden="true" />
<span className="sr-only">Close</span>
</button>
AI instruction: Icon-only buttons MUST have aria-label. Never generate a button with only an icon and no text alternative.
Rule 3 — Forms Need Labels
// ❌ No label — screen reader can't identify field
<input type="email" placeholder="Enter email" />
// ✅ Explicit label
<label htmlFor="email">Email address</label>
<input id="email" type="email" placeholder="your@email.com" />
// ✅ aria-label when visual label not possible
<input
type="search"
aria-label="Search products"
placeholder="Search..."
/>
// ✅ Error messages linked with aria-describedby
<input
id="email"
type="email"
aria-describedby="email-error"
aria-invalid={hasError}
/>
{hasError && <span id="email-error" role="alert">Invalid email</span>}
AI instruction: Every form input MUST have either a <label> with matching htmlFor/id, or an aria-label. Placeholder text is NOT a label.
Rule 4 — Interactive Elements Need Focus Management
// ❌ div as button — not keyboard accessible
<div className="btn">Click me</div>
// ✅ Use semantic button
<button className="btn">Click me</button>
// ✅ If div must be interactive
<div
role="button"
tabIndex={0}
=> e.key === 'Enter' && handleClick()}
>
Click me
</div>
AI instruction: Never generate onClick on a div or span without also adding role="button", tabIndex={0}, and onKeyDown handler.
Rule 5 — Modals/Dialogs Need Focus Trap
// ✅ Dialog with proper accessibility
function Modal({ isOpen, onClose, title, children }) {
const firstFocusRef = useRef(null);
useEffect(() => {
if (isOpen) firstFocusRef.current?.focus();
}, [isOpen]);
return isOpen ? (
<div
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
=> e.key === 'Escape' && onClose()}
>
<h2 id="modal-title">{title}</h2>
<button ref={firstFocusRef} aria-label="Close modal">×</button>
{children}
</div>
) : null;
}
Rule 6 — Colour Contrast
/* ❌ Fails WCAG AA (contrast ratio < 4.5:1) */
.text { color: #999; background: #fff; }
/* ✅ Passes WCAG AA (contrast ratio > 4.5:1) */
.text { color: #595959; background: #fff; }
/* ✅ Large text only needs 3:1 ratio */
.heading { color: #767676; font-size: 1.5rem; font-weight: bold; }
AI instruction: Text colours must meet WCAG AA contrast. Never use light grey on white for body text.
Rule 7 — Live Regions for Dynamic Content
// ❌ Status update — screen reader doesn't announce it
<div className="status">{statusMessage}</div>
// ✅ aria-live announces changes automatically
<div aria-live="polite" aria-atomic="true">{statusMessage}</div>
// ✅ For urgent alerts
<div role="alert">{errorMessage}</div>
Companion Script
npx reactforge check-accessibility ./src
Scans all JSX/TSX files and reports accessibility violations with line numbers.
Quick Reference Checklist
- All images have alt text (or
alt=""if decorative) - All buttons have text or
aria-label - All form inputs have
<label>oraria-label - No
onClickon non-interactive elements without keyboard support - Modals trap focus and close on Escape
- Dynamic content uses
aria-liveorrole="alert" - Colour contrast meets WCAG AA (4.5:1 for text)
- Page has a single
<h1>, heading hierarchy is correct