Skip to main content
RapidDev - Software Development Agency
cursor-tutorial

How to Generate Accessible UI with Cursor

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.

What you'll learn

  • How to create .cursorrules that enforce ARIA attributes and accessibility
  • How to prompt Cursor to generate accessible React components
  • How to use @Docs to reference WAI-ARIA guidelines
  • How to audit Cursor-generated components for accessibility issues
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner6 min read10 minCursor Pro+, any projectMarch 2026RapidDev Engineering Team
TL;DR

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

1

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.

.cursorrules
1# .cursorrules
2
3## 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, footer
7- All form inputs must have associated labels (htmlFor + id)
8- Use aria-label on icon-only buttons
9- Use aria-live for dynamic content updates
10- Color contrast must meet 4.5:1 minimum ratio
11- Focus must be visible on all interactive elements
12- Modals must trap focus and return focus on close
13- Use role attributes only when semantic HTML is insufficient

Pro 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.

2

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.

Cursor Composer prompt
1// Prompt to type in Cursor Composer (Cmd+I):
2// @src/components/shared/AccessibleModal.tsx
3// Generate a new Dropdown component with full accessibility:
4// - Keyboard navigation (arrow keys, Enter, Escape)
5// - aria-expanded, aria-haspopup, aria-activedescendant
6// - Focus trap when open, return focus on close
7// - Screen reader announcements for selection changes
8// - Follow WAI-ARIA Combobox pattern
9// 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.

3

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.

Cmd+K inline prompt
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.

4

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.

.cursor/rules/accessibility.mdc
1---
2description: Accessibility enforcement for UI components
3globs: "src/components/**, src/pages/**, **/*.tsx"
4alwaysApply: false
5---
6
7- Every button must have accessible text (visible or aria-label)
8- Every input must have a label element with matching htmlFor
9- Use semantic HTML elements before adding ARIA roles
10- Modals: use dialog role, trap focus, Escape to close
11- Lists: use ul/ol with li, not div with div
12- Headings: maintain proper hierarchy (h1 h2 h3)
13- Forms: group related fields with fieldset and legend
14- Error messages: associate with inputs via aria-describedby

Expected result: Accessibility rules auto-attach for all component and page files.

Complete working example

src/components/shared/AccessibleDropdown.tsx
1import { useState, useRef, useEffect, KeyboardEvent } from 'react';
2
3interface DropdownProps {
4 label: string;
5 options: { value: string; label: string }[];
6 value: string;
7 onChange: (value: string) => void;
8}
9
10export 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);
15
16 const selectedOption = options.find((o) => o.value === value);
17
18 useEffect(() => {
19 if (isOpen && listRef.current) {
20 const active = listRef.current.children[activeIndex] as HTMLElement;
21 active?.scrollIntoView({ block: 'nearest' });
22 }
23 }, [activeIndex, isOpen]);
24
25 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 };
53
54 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 <button
60 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 <ul
71 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 <li
79 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.

ChatGPT Prompt

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.

Cursor Prompt

@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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.