| name | react-accessibility-validator |
| description | Automatically validate React/Next.js components meet WCAG 2.1 AA standards. Use when creating components, forms, buttons, modals, navigation, or any interactive UI elements. |
Accessibility Validator
Auto-enforces WCAG 2.1 AA accessibility standards for all React/Next.js components.
Activation Triggers
This skill activates when:
- Creating React/Next.js components
- Building forms, buttons, modals, navigation
- Adding interactive elements
- Mentioning "component", "UI", "form", "button"
- Creating pages or layouts
- Working on user-facing features
WCAG 2.1 AA Requirements (MANDATORY)
All components MUST meet:
- ✅ Keyboard navigation - All interactive elements focusable and usable via keyboard
- ✅ Semantic HTML - Proper HTML elements and ARIA attributes
- ✅ Color contrast - 4.5:1 for normal text, 3:1 for large text
- ✅ Screen reader support - All content accessible to assistive technologies
- ✅ Focus management - Visible focus indicators, logical tab order
- ✅ Form accessibility - Labels, error announcements, help text
Auto-Validation Process
Step 1: Detect Component Creation
When detecting component being written:
function LoginButton({ onClick }) {
return <div onClick={onClick}>Login</div>
}
Step 2: Run Accessibility Checklist
Verify:
- ❌ Using div instead of button (semantic HTML)
- ❌ No keyboard support (only onClick, no onKeyDown)
- ❌ Not focusable (div not in tab order)
- ❌ No ARIA attributes
- ❌ No focus styles
Step 3: Auto-Fix Violations
Before (Inaccessible):
function LoginButton({ onClick }) {
return <div onClick={onClick}>Login</div>
}
After (Accessible):
function LoginButton({ onClick }) {
return (
<button
onClick={onClick}
type="button"
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
>
Login
</button>
)
}
Step 4: Explain Fixes
Accessibility Violations Fixed
Changes:
- ✅ Changed
<div> to <button> (semantic HTML)
- ✅ Added
type="button" (prevents form submission)
- ✅ Button is now keyboard accessible (Enter/Space keys work)
- ✅ Automatically focusable (in tab order)
- ✅ Added visible focus ring (focus:ring-2)
Why:
- Buttons are natively accessible to keyboard and screen readers
- Focus ring shows keyboard users where they are
- Proper semantics help assistive technologies
Common Accessibility Patterns
Pattern 1: Buttons vs Links
<div onClick={handleClick}>Click me</div>
<a href="#" onClick={handleClick}>Click me</a>
<button onClick={handleClick} type="button">
Click me
</button>
<Link href="/page">Go to page</Link>
Pattern 2: Form Labels
<input type="email" placeholder="Email" />
<input type="email" placeholder="Enter your email" />
<label htmlFor="email" className="block text-sm font-medium">
Email Address
</label>
<input
id="email"
type="email"
className="mt-1 block w-full rounded-md border-gray-300"
aria-required="true"
/>
<label htmlFor="email" className="block text-sm font-medium">
Email Address
</label>
<input
id="email"
type="email"
aria-invalid={!!errors.email}
aria-describedby={errors.email ? "email-error" : undefined}
/>
{errors.email && (
<p id="email-error" className="mt-1 text-sm text-red-600" role="alert">
{errors.email.message}
</p>
)}
Pattern 3: Modal Dialogs
import { Dialog } from '@headlessui/react'
function Modal({ isOpen, onClose, title, children }) {
return (
<Dialog
open={isOpen}
onClose={onClose}
className="relative z-50"
>
{/* Backdrop */}
<div className="fixed inset-0 bg-black/30" aria-hidden="true" />
{/* Modal */}
<div className="fixed inset-0 flex items-center justify-center p-4">
<Dialog.Panel className="bg-white rounded-lg p-6 max-w-md">
<Dialog.Title className="text-lg font-semibold">
{title}
</Dialog.Title>
<div className="mt-4">
{children}
</div>
<button
onClick={onClose}
className="mt-4 px-4 py-2 bg-gray-200 rounded"
aria-label="Close dialog"
>
Close
</button>
</Dialog.Panel>
</div>
</Dialog>
)
}
Pattern 4: Icon Buttons
<button>
<XIcon />
</button>
<button aria-label="Close" type="button">
<XIcon className="h-5 w-5" aria-hidden="true" />
</button>
<button type="button" className="relative">
<span className="sr-only">Close</span>
<XIcon className="h-5 w-5" aria-hidden="true" />
</button>
Pattern 5: Loading States
{isLoading && <Spinner />}
{isLoading && (
<div role="status" aria-live="polite">
<Spinner />
<span className="sr-only">Loading...</span>
</div>
)}
Pattern 6: Images
<img src="/logo.png" />
<img src="/logo.png" alt="Company Logo" />
<img src="/decoration.png" alt="" aria-hidden="true" />
Keyboard Navigation Checklist
For every component, verify:
- ✅ All interactive elements are focusable (button, a, input, etc.)
- ✅ Tab order is logical (follows visual flow)
- ✅ Focus is visible (outline, ring, or custom indicator)
- ✅ No keyboard traps (can escape from all UI)
- ✅ Enter/Space work on buttons
- ✅ Escape closes modals/dropdowns
- ✅ Arrow keys work in lists/menus
Color Contrast Requirements
Check all text meets minimum contrast:
<p className="text-gray-400 bg-white">Low contrast text</p>
<p className="text-gray-900 bg-white">High contrast text</p>
<h1 className="text-2xl text-gray-600 bg-white">Large heading</h1>
Minimum Ratios:
- Normal text (< 18pt): 4.5:1
- Large text (≥ 18pt or bold ≥ 14pt): 3:1
- UI components: 3:1
Screen Reader Support
Landmark Regions
<header role="banner">
<nav role="navigation" aria-label="Main navigation">
{/* nav items */}
</nav>
</header>
<main role="main">
{/* main content */}
</main>
<aside role="complementary" aria-label="Related content">
{/* sidebar */}
</aside>
<footer role="contentinfo">
{/* footer */}
</footer>
ARIA Labels
<button aria-label="Add item to cart">
<PlusIcon aria-hidden="true" />
</button>
<nav aria-label="Breadcrumb">
<ol>
<li><a href="/">Home</a></li>
<li aria-current="page">Products</li>
</ol>
</nav>
Live Regions
<div role="alert" aria-live="assertive">
Error: Please fill in all required fields
</div>
<div role="status" aria-live="polite">
5 items in cart
</div>
Focus Management
'use client'
import { useEffect, useRef } from 'react'
function Modal({ isOpen, title }) {
const titleRef = useRef<HTMLHeadingElement>(null)
useEffect(() => {
if (isOpen && titleRef.current) {
titleRef.current.focus()
}
}, [isOpen])
return (
<div role="dialog" aria-modal="true">
<h2 ref={titleRef} tabIndex={-1} className="outline-none">
{title}
</h2>
{/* content */}
</div>
)
}
Testing Recommendations
Suggest automated tests:
import { render } from '@testing-library/react'
import { axe, toHaveNoViolations } from 'jest-axe'
expect.extend(toHaveNoViolations)
test('LoginButton has no accessibility violations', async () => {
const { container } = render(<LoginButton onClick={() => {}} />)
const results = await axe(container)
expect(results).toHaveNoViolations()
})
Common Violations Checked
Missing Labels
- Form inputs without labels
- Icon buttons without aria-label
- Images without alt text
Poor Semantics
- Divs instead of buttons
- Links instead of buttons
- Missing heading hierarchy
Keyboard Issues
- Elements not in tab order
- Missing focus indicators
- Keyboard traps
Screen Reader Issues
- Missing ARIA labels
- Incorrect ARIA roles
- No live region announcements
Color Issues
- Insufficient contrast
- Color-only information
- Missing text alternatives
Integration with Tailwind
<span className="sr-only">Accessible description</span>
<button className="focus:outline-none focus:ring-2 focus:ring-blue-500">
Click me
</button>
<p className="text-gray-900 dark:text-gray-100">
Good contrast in both modes
</p>
Success Criteria
✅ All interactive elements keyboard accessible
✅ All images have alt text
✅ All forms have labels
✅ Color contrast ≥ 4.5:1 (normal text)
✅ Proper semantic HTML used
✅ ARIA attributes where needed
✅ Focus indicators visible
✅ Screen reader tested
✅ Automated axe tests pass
Behavior
Proactive enforcement:
- Check accessibility without being asked
- Fix violations immediately
- Add ARIA attributes automatically
- Explain WCAG criteria for each fix
- Suggest automated tests
Never:
- Require explicit "check accessibility" request
- Wait for accessibility audit
- Just warn without fixing
Block completion if:
- Buttons are divs
- Forms lack labels
- Color contrast fails
- Icon buttons lack accessible names
- Images missing alt text
This ensures every component is accessible from day one.