This skill should be used when the user says "fix accessibility", "a11y audit", "make this accessible", "keyboard navigation broken", "screen reader support", "WCAG compliance", "fix focus states", "add aria labels", "semantic HTML", "color contrast issues", "accessibility review", "fix tab order", or any request to audit or improve the accessibility of a web interface.
How this skill is triggered — by the user, by Claude, or both
Slash command
/saas-frontend-designer:accessibility-auditThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Audit and fix web interfaces for WCAG 2.1 AA compliance. Run this as a systematic checklist after generating or polishing UI code.
Audit and fix web interfaces for WCAG 2.1 AA compliance. Run this as a systematic checklist after generating or polishing UI code.
Replace non-semantic elements with their correct counterparts.
Critical fixes:
// ❌ Div as button
<div onClick={handleClick} className="cursor-pointer">Click me</div>
// ✅ Native button
<button onClick={handleClick}>Click me</button>
// ❌ Div as link
<div onClick={() => router.push('/about')}>About</div>
// ✅ Native anchor
<Link href="/about">About</Link>
// ❌ Div as list
<div>{items.map(i => <div key={i.id}>{i.name}</div>)}</div>
// ✅ Semantic list
<ul>{items.map(i => <li key={i.id}>{i.name}</li>)}</ul>
Element selection rules:
<button><a> / <Link><main> (one per page)<nav> with aria-label when multiple<section> with heading<aside><header><footer>Headings must follow logical order: h1 → h2 → h3. Never skip levels for styling.
Rules:
<h1> per page (the page title)<section> should begin with a heading// ❌ Wrong: skipping levels for style
<h1>Dashboard</h1>
<h3>Recent Activity</h3> {/* Skipped h2 */}
// ✅ Correct: logical hierarchy, styled with CSS
<h1 className="text-2xl font-bold">Dashboard</h1>
<h2 className="text-lg font-semibold">Recent Activity</h2>
Every interactive element must be operable via keyboard.
Requirements:
Fix pattern for custom interactive elements:
<div
role="button"
tabIndex={0}
onClick={handleClick}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
handleClick()
}
}}
>
Custom button
</div>
Better: replace with <button> which handles this natively.
Focus visibility: Every focusable element must have a visible focus indicator.
// Standard focus ring (never remove without replacement)
className="focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
// Never do this without a replacement
className="outline-none" // ❌ Makes element invisible to keyboard users
Focus trapping: Modals and dialogs must trap focus inside while open. When a modal opens, focus moves to the first focusable element inside. When it closes, focus returns to the trigger element. shadcn/ui Dialog handles this automatically.
Skip link: Add a skip-to-content link as the first focusable element on the page.
<a
href="#main-content"
className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:bg-white focus:px-4 focus:py-2 focus:rounded-md focus:shadow-md focus:text-sm focus:font-medium"
>
Skip to main content
</a>
{/* ... navigation ... */}
<main id="main-content">
{/* Page content */}
</main>
All text must meet minimum contrast ratios against its background.
| Text type | Minimum ratio | Example |
|---|---|---|
| Body text (< 18px) | 4.5:1 | gray-600 on white ✅ |
| Large text (≥ 18px bold or ≥ 24px) | 3:1 | gray-500 on white ✅ |
| UI components (borders, icons) | 3:1 | gray-400 on white ⚠️ borderline |
| Placeholder text | 4.5:1 | gray-400 on white ❌ |
Common failures:
text-gray-400 on white background → fails for body text (use text-gray-500 or text-gray-600)placeholder:text-gray-400 → acceptable because placeholders have additional context from labels// Informative image: descriptive alt text
<Image src="/chart.png" alt="Monthly revenue chart showing 23% growth" />
// Decorative image: empty alt
<Image src="/pattern.svg" alt="" aria-hidden="true" />
// Icon buttons: aria-label required
<button aria-label="Close dialog">
<X className="h-4 w-4" aria-hidden="true" />
</button>
// Complex images: use figcaption
<figure>
<Image src="/architecture.png" alt="System architecture diagram" />
<figcaption className="mt-2 text-sm text-gray-500">
Figure 1: Overview of the microservices architecture
</figcaption>
</figure>
Every form input must have an associated label.
// ✅ Explicit label association
<label htmlFor="email" className="text-sm font-medium text-gray-700">
Email address
</label>
<input id="email" type="email" aria-describedby="email-error" />
<p id="email-error" role="alert" className="text-sm text-red-600">
Please enter a valid email.
</p>
// ✅ Required field indication
<label htmlFor="name">
Name <span aria-hidden="true" className="text-red-500">*</span>
<span className="sr-only">(required)</span>
</label>
<input id="name" required aria-required="true" />
Error announcement: Use role="alert" or aria-live="polite" on error messages so screen readers announce them.
Use ARIA only when native HTML semantics are insufficient.
Common patterns:
// Live region for dynamic content
<div aria-live="polite" aria-atomic="true">
{statusMessage}
</div>
// Current page in navigation
<nav aria-label="Main navigation">
<a href="/dashboard" aria-current="page">Dashboard</a>
<a href="/settings">Settings</a>
</nav>
// Expandable content
<button aria-expanded={isOpen} aria-controls="panel-content">
Show details
</button>
<div id="panel-content" hidden={!isOpen}>
{/* Content */}
</div>
// Table with caption
<table>
<caption className="sr-only">Monthly revenue by product</caption>
<thead>...</thead>
<tbody>...</tbody>
</table>
Verify these read correctly with a screen reader (VoiceOver on Mac: Cmd+F5):
Consult references/wcag-checklist.md for the complete WCAG 2.1 AA success criteria mapped to common SaaS patterns.
npx claudepluginhub moxywolfllc/moxywolf-plugins --plugin saas-frontend-designerCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.