Comprehensive Rules for building, testing, and maintaining WCAG 2.1 AA–compliant web interfaces in HTML, CSS, TypeScript, and React.
You're tired of accessibility issues slipping into production. Of retrofitting components months later when compliance audits surface critical violations. Of spending entire sprints fixing accessibility debt that could have been prevented with the right development workflow.
Here's the reality: One in four adults in the US has a disability that impacts their web experience. Your current development process treats accessibility as an afterthought—something to "check later" or handle in a separate ticket. This approach costs you:
The core problem? Most teams lack a systematic approach to weave WCAG compliance directly into their development workflow.
These Cursor Rules transform your codebase into an accessibility-compliant powerhouse. Instead of bolting on accessibility later, you'll build WCAG 2.1 AA compliance into every component, form, and interaction from the ground up.
What you get:
This isn't just about avoiding lawsuits—it's about building better software that works for everyone while reducing your technical debt.
// Typical approach - accessibility as an afterthought
function LoginForm() {
return (
<div className="form">
<div>Username</div>
<input type="text" />
<div>Password</div>
<input type="password" />
<button onClick={handleSubmit}>Login</button>
</div>
);
}
// Result: Screen readers can't navigate, no error handling, fails WCAG
// With these rules - WCAG compliance built in
function LoginForm() {
return (
<form onSubmit={handleSubmit} aria-live="polite">
<h1>Login to Your Account</h1>
{errors.summary && (
<div role="alert" id="error-summary">
<h2>Please correct the following errors:</h2>
<ul>
{errors.fields.map(error => (
<li key={error.field}>
<a href={`#${error.field}`}>{error.message}</a>
</li>
))}
</ul>
</div>
)}
<label htmlFor="username">Username</label>
<input
id="username"
type="text"
aria-invalid={errors.username ? "true" : "false"}
aria-describedby={errors.username ? "username-error" : undefined}
/>
{errors.username && (
<div id="username-error" role="alert">{errors.username}</div>
)}
<button type="submit">Login</button>
</form>
);
}
// Result: Full keyboard navigation, screen reader support, WCAG 2.1 AA compliant
Every pull request automatically runs comprehensive accessibility audits:
// Integrated into your test suite
describe('LoginForm Accessibility', () => {
it('meets WCAG 2.1 AA standards', async () => {
const { container } = render(<LoginForm />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
Impact: Zero accessibility regressions reach production. Your CI/CD pipeline becomes an accessibility quality gate that prevents violations before they ship.
// Automatic focus management and error announcements
const handleFormError = (errors: ValidationErrors) => {
// Focus moves to error summary
errorSummaryRef.current?.focus();
// Screen readers announce errors
setAriaLiveMessage(`${errors.length} errors found. Please review and correct.`);
// Each field gets proper ARIA attributes
return errors.map(error => ({
...error,
ariaInvalid: true,
ariaDescribedBy: `${error.field}-error`
}));
};
Result: Users with disabilities get the same smooth error experience as everyone else. No more broken form flows or confused screen reader users.
Build complex UI components that work perfectly with assistive technology:
// Modal with complete accessibility built in
export const Modal = forwardRef<HTMLDivElement, ModalProps>(
({ isOpen, onClose, title, children }, ref) => {
const modalRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (isOpen) {
// Trap focus inside modal
modalRef.current?.focus();
// Prevent body scroll
document.body.style.overflow = 'hidden';
}
return () => {
document.body.style.overflow = 'unset';
};
}, [isOpen]);
return (
<div
ref={modalRef}
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
tabIndex={-1}
>
<h2 id="modal-title">{title}</h2>
{children}
</div>
);
}
);
## Acceptance Criteria (Generated automatically)
- [ ] All interactive elements reachable via keyboard
- [ ] Screen reader announces form errors clearly
- [ ] Color contrast meets 4.5:1 ratio for all text
- [ ] Component passes axe-core audit with 0 violations
- [ ] Focus management works correctly for dynamic content
Time Saved: 2-3 hours per story that would otherwise be spent writing accessibility acceptance criteria manually.
Your PR automatically includes:
Developer Experience: No more "Did anyone test this with a screen reader?" questions. Accessibility validation happens automatically.
// Every component exports accessibility metadata
export const Button = {
component: ButtonComponent,
accessibility: {
keyboardSupport: ['Enter', 'Space'],
ariaRoles: ['button'],
contrastRatio: '4.5:1',
screenReaderTested: true
}
};
Result: Your design system becomes a force multiplier for accessibility across all your products.
Copy the rules into your .cursorrules file. That's it—Cursor now generates WCAG-compliant code by default.
npm install --save-dev jest-axe @testing-library/react cypress-axe
# .github/workflows/accessibility.yml
- name: Accessibility Audit
run: |
npm run test:a11y
npm run cypress:a11y
# Fails build on any critical violations
// Track violations in production
window.addEventListener('load', async () => {
const results = await axe.run();
if (results.violations.length > 0) {
analytics.track('accessibility_violation', {
violations: results.violations.length,
page: window.location.pathname
});
}
});
These rules don't just help you avoid lawsuits—they transform how you think about building software. When accessibility is woven into your development process from the start, you build better products for everyone.
Stop treating accessibility as technical debt. Start building it into every component, every form interaction, and every user flow. Your users—all of them—will thank you.
Ready to eliminate accessibility issues from your development workflow? Install these Cursor Rules and experience what it's like to ship accessible code by default.
You are an expert in HTML5, CSS3, TypeScript 4+, React 18, WAI-ARIA, WCAG 2.1 AA/2.2, emerging WCAG 3.0, EN 301 549, axe-core, Jest, and Testing Library.
Key Principles
- Conform to WCAG 2.1 AA by default; track WCAG 2.2 & 3.0 drafts for upcoming changes.
- Map every UI requirement to POUR (Perceivable, Operable, Understandable, Robust).
- Prefer native, semantic HTML over ARIA; use ARIA only when semantics are missing.
- Design keyboard-first; all interactive elements must be reachable, operable, and visually apparent via keyboard.
- Content and interaction must remain functional with CSS turned off, high contrast mode on, or screen zoom at 200 %.
- Never embed text in images; provide programmatic alternatives for all non-text content.
- Automate everything (axe-core, pa11y, Lighthouse); supplement with manual audits each sprint.
- Accessibility acceptance criteria are non-negotiable; code cannot merge unless all automated checks pass at 0 critical violations.
HTML
- Use proper landmark order: <header>, <nav>, <main>, <aside>, <footer>. Only one <main> per page.
- Provide unique, descriptive <title> and first-level <h1> per route.
- ALT attributes: meaningful for informative images, empty (alt="") for decorative ones.
- Forms: associate <label for> with every <input>, include aria-invalid="true" & aria-describedby for errors.
- Tables: use <th scope> & <caption>; never use <div> for tabular data.
- Headings must not skip levels (e.g., do not jump from <h2> to <h4>).
CSS
- Maintain 4.5:1 contrast ratio for normal text, 3:1 for large text; reference Token names (e.g., color-text-primary) in design system.
- Support prefers-reduced-motion: limit animations to <200 ms unless essential, and respect the media query to disable.
- Focus outlines: Use :focus-visible; never remove outline without providing a visible alternative ≥2 px.
- Do not lock zoom/pinch; ensure layouts reflow at 320 px width.
TypeScript / JavaScript
- Never attach click handlers to non-interactive elements unless role="button" + tabindex=0 + keydown/keyup handlers.
- Manage focus explicitly after DOM mutations (e.g., dialogs, toast) using React ref and .focus().
- Use aria-live="polite"/"assertive" regions for dynamic content updates.
- Validate user input client-side and server-side; surface error summary at the top and inline errors beside each field.
- Avoid keyCode; use event.key for cross-device compatibility.
Error Handling & Validation
- Throw A11yError objects containing userMessage, developerMessage, and remediationHint.
- On form submit failure:
• Shift focus to the error summary container (role="alert").
• List errors as <li><a href="#fieldID">Issue description</a></li>.
• Mark each erroneous field with aria-invalid and link to detailed help via aria-describedby.
- In React, centralize error handling with an <ErrorBoundary> that logs to Sentry and re-renders an accessible message.
React 18 Rules
- Functional components only; enable strict mode.
- Wrap custom components (e.g., <Modal>, <Select>) with forwardRef & expose imperative handleFocus().
- Use React-Aria or Radix primitives when possible; otherwise, mirror their API contracts.
- Dialogs:
• role="dialog" + aria-modal="true" + aria-labelledby & aria-describedby.
• Trap focus with focus-lock; release on close & return to triggering element.
- Lists generated via map() must include key and, when interactive, role="listbox"/"menu" with correct ARIA.
Testing
- Unit: Use @testing-library/react-hooks & jest-axe (expect(await axe(container)).toHaveNoViolations()).
- Integration: Run axe-core in Cypress for each CI build (threshold: 0 serious/critical).
- Manual: Screen-reader pass (NVDA + Firefox, VoiceOver + Safari) on every major flow.
- CI: Break build on any regression tagged as critical, post slack summary with diff.
Performance
- Lazy-load heavy media; provide alternative text while loading.
- Prefer CSS over JS for animations; ensure 60fps and accessibility safe-stop.
- Real-time captions: use Web Speech API fallback to server speech-to-text if local unsupported.
Security & Legal
- Log accessibility violations with audit trail; required for ADA / Section 508 evidence.
- Store compliance reports quarterly; keep for 5 years.
- Update VPAT when code affecting accessibility lands in production.
Documentation
- Each component page in Storybook must include an Accessibility tab with:
• Interactive keyboard demo
• Supported ARIA roles/props table
• Color contrast checklist
- Maintain CHANGELOG.md section "[a11y]" for any accessibility-relevant change.
Folder Structure Example
components/
└─ dialog/
├─ dialog.tsx ← core component
├─ dialog.stories.tsx
├─ dialog.a11y.test.tsx
└─ dialog.types.ts
Continuous Improvement
- Schedule bi-annual 3rd-party audit; triage findings within 2 weeks.
- Track WCAG 3.0 drafts; map new outcomes to backlog tickets.
- Run internal brown-bags quarterly to train dev & design teams on latest A11y patterns.