How to generate accessible UI with Cursor. How to create .cursorrules that enforce ARIA attributes and accessibility. How to prompt Cursor to generate accessible React components. Configure .cursorrules and use @file context in Cursor Chat (Cmd+L) or Composer (Cmd+I) for best results.
Why Cursor Skips Accessibility by Default
Cursor generates functional components but often omits ARIA attributes, keyboard navigation, focus management, and screen reader labels. Adding accessibility rules to your project ensures every generated component meets WCAG guidelines. This tutorial covers .cursorrules for a11y enforcement, prompting patterns for accessible UI, and post-generation auditing with axe-core.
Prerequisites
- Cursor installed (Free or Pro)
- A React project with UI components
- Basic understanding of WCAG and ARIA attributes
- Optional: axe-core or eslint-plugin-jsx-a11y installed
Step-by-step guide
Add accessibility rules to .cursorrules
Add accessibility rules to .cursorrules
Create rules that enforce ARIA attributes, keyboard navigation, and semantic HTML in all generated components. Specify that all interactive elements must be keyboard accessible and all images must have alt text.
1# .cursorrules23## Accessibility Rules (WCAG 2.1 AA)4- All interactive elements must be keyboard accessible (tabIndex, onKeyDown)5- All images must have descriptive alt text (never alt="")6- Use semantic HTML: nav, main, section, article, aside, header, footer7- All form inputs must have associated labels (htmlFor + id)8- Use aria-label on icon-only buttons9- Use aria-live for dynamic content updates10- Color contrast must meet 4.5:1 minimum ratio11- Focus must be visible on all interactive elements12- Modals must trap focus and return focus on close13- Use role attributes only when semantic HTML is insufficientPro tip: Add eslint-plugin-jsx-a11y to your project. Cursor can then use @Lint Errors to catch accessibility violations after generation.
Expected result: Cursor includes ARIA attributes and semantic HTML in all generated components.
Generate accessible components with Composer
Generate accessible components with Composer
When generating UI components, explicitly request accessibility features. Reference your existing accessible components with @file so Cursor follows the same patterns.
1// Prompt to type in Cursor Composer (Cmd+I):2// @src/components/shared/AccessibleModal.tsx3// Generate a new Dropdown component with full accessibility:4// - Keyboard navigation (arrow keys, Enter, Escape)5// - aria-expanded, aria-haspopup, aria-activedescendant6// - Focus trap when open, return focus on close7// - Screen reader announcements for selection changes8// - Follow WAI-ARIA Combobox pattern9// Use the same structure as AccessibleModal.tsx.Pro tip: Reference the WAI-ARIA authoring practices with @Docs if you have indexed the W3C documentation.
Expected result: A fully accessible dropdown component with proper ARIA attributes and keyboard handling.
Audit generated components with Cmd+K
Audit generated components with Cmd+K
After generating a component, select it and use Cmd+K to ask Cursor to audit it for accessibility issues. Cursor will identify missing ARIA attributes, keyboard gaps, and semantic HTML improvements.
1// Select a component in your editor, press Cmd+K:2// Audit this component for WCAG 2.1 AA accessibility issues.3// Check for: missing ARIA attributes, keyboard navigation gaps,4// semantic HTML improvements, color contrast issues, focus management.5// Fix all issues and explain each change.Expected result: Cursor identifies and fixes accessibility issues in the selected component.
Create an auto-attaching a11y rule for components
Create an auto-attaching a11y rule for components
Create a .cursor/rules/accessibility.mdc that auto-attaches for all component files, ensuring accessibility standards apply to every generated component.
1---2description: Accessibility enforcement for UI components3globs: "src/components/**, src/pages/**, **/*.tsx"4alwaysApply: false5---67- Every button must have accessible text (visible or aria-label)8- Every input must have a label element with matching htmlFor9- Use semantic HTML elements before adding ARIA roles10- Modals: use dialog role, trap focus, Escape to close11- Lists: use ul/ol with li, not div with div12- Headings: maintain proper hierarchy (h1 → h2 → h3)13- Forms: group related fields with fieldset and legend14- Error messages: associate with inputs via aria-describedbyExpected result: Accessibility rules auto-attach for all component and page files.
Complete working example
1import { useState, useRef, useEffect, KeyboardEvent } from 'react';23interface DropdownProps {4 label: string;5 options: { value: string; label: string }[];6 value: string;7 onChange: (value: string) => void;8}910export function AccessibleDropdown({ label, options, value, onChange }: DropdownProps): JSX.Element {11 const [isOpen, setIsOpen] = useState(false);12 const [activeIndex, setActiveIndex] = useState(-1);13 const buttonRef = useRef<HTMLButtonElement>(null);14 const listRef = useRef<HTMLUListElement>(null);1516 const selectedOption = options.find((o) => o.value === value);1718 useEffect(() => {19 if (isOpen && listRef.current) {20 const active = listRef.current.children[activeIndex] as HTMLElement;21 active?.scrollIntoView({ block: 'nearest' });22 }23 }, [activeIndex, isOpen]);2425 const handleKeyDown = (e: KeyboardEvent): void => {26 switch (e.key) {27 case 'ArrowDown':28 e.preventDefault();29 if (!isOpen) setIsOpen(true);30 setActiveIndex((i) => Math.min(i + 1, options.length - 1));31 break;32 case 'ArrowUp':33 e.preventDefault();34 setActiveIndex((i) => Math.max(i - 1, 0));35 break;36 case 'Enter':37 case ' ':38 e.preventDefault();39 if (isOpen && activeIndex >= 0) {40 onChange(options[activeIndex].value);41 setIsOpen(false);42 buttonRef.current?.focus();43 } else {44 setIsOpen(true);45 }46 break;47 case 'Escape':48 setIsOpen(false);49 buttonRef.current?.focus();50 break;51 }52 };5354 return (55 <div className="relative" onKeyDown={handleKeyDown}>56 <label id={`${label}-label`} className="block text-sm font-medium mb-1">57 {label}58 </label>59 <button60 ref={buttonRef}61 aria-haspopup="listbox"62 aria-expanded={isOpen}63 aria-labelledby={`${label}-label`}64 onClick={() => setIsOpen(!isOpen)}65 className="w-full border rounded px-3 py-2 text-left focus:ring-2"66 >67 {selectedOption?.label || 'Select an option'}68 </button>69 {isOpen && (70 <ul71 ref={listRef}72 role="listbox"73 aria-labelledby={`${label}-label`}74 aria-activedescendant={activeIndex >= 0 ? `option-${activeIndex}` : undefined}75 className="absolute w-full border rounded mt-1 bg-white shadow-lg z-10"76 >77 {options.map((option, index) => (78 <li79 key={option.value}80 id={`option-${index}`}81 role="option"82 aria-selected={option.value === value}83 className={`px-3 py-2 cursor-pointer ${index === activeIndex ? 'bg-blue-100' : ''} ${option.value === value ? 'font-bold' : ''}`}84 onClick={() => { onChange(option.value); setIsOpen(false); buttonRef.current?.focus(); }}85 >86 {option.label}87 </li>88 ))}89 </ul>90 )}91 </div>92 );93}Common mistakes when generating Accessible UI with Cursor
Why it's a problem: Using div and span for all interactive elements
How to avoid: Add a rule requiring semantic HTML: button for clickable actions, a for navigation, input for data entry. Only use divs for layout.
Why it's a problem: Adding empty alt text to decorative images only
How to avoid: Specify in your rules: 'Informative images need descriptive alt text. Only decorative images use alt="". Icons in buttons need aria-label on the button.'
Why it's a problem: Not testing keyboard navigation after generation
How to avoid: Always test Tab, Enter, Escape, and arrow key navigation after generating interactive components.
Best practices
- Add WCAG 2.1 AA accessibility rules to .cursorrules with specific ARIA requirements
- Reference existing accessible components as templates when generating new ones
- Use semantic HTML elements before adding ARIA roles
- Test keyboard navigation on all generated interactive components
- Use @Lint Errors with eslint-plugin-jsx-a11y for post-generation auditing
- Include focus management rules for modals, dropdowns, and menus
- Create auto-attaching .cursor/rules/ for component directories with a11y requirements
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Generate a fully accessible React dropdown component following WAI-ARIA Combobox pattern. Include: keyboard navigation (arrows, Enter, Escape), aria-expanded, aria-haspopup, aria-activedescendant, focus trap, screen reader announcements.
@src/components/shared/AccessibleModal.tsx Generate an accessible Dropdown component following the same a11y patterns. Include keyboard navigation, ARIA attributes, focus management. Follow WAI-ARIA Listbox pattern. Use semantic HTML elements.
Frequently asked questions
Does Cursor add ARIA attributes by default?
No. Cursor generates functional components but typically omits ARIA attributes, keyboard handlers, and focus management unless explicitly instructed. Add accessibility rules to .cursorrules for automatic enforcement.
Can Cursor generate components that pass axe-core audits?
Yes, with proper rules. Add WCAG requirements to .cursorrules and audit generated components with @Lint Errors. For complex components, follow up with an explicit accessibility audit prompt.
Should I use ARIA or semantic HTML?
Prefer semantic HTML first (button, nav, main, dialog). Add ARIA attributes only when HTML semantics are insufficient. Cursor follows this principle when your rules state: 'Use semantic HTML before ARIA roles.'
How do I make Cursor generate keyboard-navigable components?
Add specific rules: 'All interactive elements must handle Enter, Escape, and arrow keys where appropriate.' Reference an existing keyboard-navigable component as a template with @file.
Can Cursor help with color contrast checking?
Cursor cannot check actual rendered colors, but it can follow rules like 'Use text-gray-900 on white backgrounds for 4.5:1 contrast.' For real contrast checking, use browser DevTools or axe-core.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation