Skip to content

React Accessibility prompt

Rules that make your coding agent build React interfaces everyone can use: native elements first, labelled controls, focus that never gets lost, and WCAG 2.2 AA contrast, targets and motion.

What it makes your agent good at

Agents put onClick on divs, paint errors red and nothing else, lose focus when a list re-renders, strip outlines with outline-none and hand-roll menus that ignore the keyboard. This prompt makes them reach for native elements and the Base UI, Radix or React Aria primitives first, wire names, errors and live regions correctly, and check their work with axe, a keyboard pass and a screen reader.

  1. Native elements firstbutton, a href, label and table before any ARIA, one h1 per page, named landmarks and a skip link to main.
  2. Names and errors wireduseId pairs, aria-invalid and aria-describedby on errors, autocomplete tokens, and async results in a live region.
  3. Focus that never gets lostDialogs that trap and restore focus, inert backgrounds, focus at the top after route changes, stable keys on re-render.
  4. Primitives over hand-rolled widgetsMenus, comboboxes, tabs and dialogs from Base UI, Radix or React Aria, which already implement the keyboard patterns.
  5. WCAG 2.2, not just 2.024px targets, focus not hidden under sticky headers, alternatives to dragging, accessible sign-in, no redundant entry.
  6. Checked, not assumedaxe in unit and end-to-end tests, a keyboard pass, 200 percent zoom, reduced motion and a screen reader spot check.

The React Accessibility rules

100 lines, current to React 19.3 and WCAG 2.2 AA, with Tailwind CSS 4.3. Last reviewed October 23, 2026. Plain Markdown, no front matter: each agent's is below.

Download

react-accessibility.md

# React Accessibility

You are building a React 19 app (often Next.js 16 with Tailwind CSS v4 and shadcn-style components on Base UI, Radix or React Aria) that must meet WCAG 2.2 level AA. Treat accessibility as correctness: a control that a keyboard or screen reader user cannot operate is a bug, not a polish item.

## Before you write code

- Check which primitives the project uses (`@base-ui/react`, `radix-ui`, `react-aria-components`) and its `components/ui/` wrappers. Build on them before writing any custom widget. Base UI ships its docs in `node_modules/@base-ui/react/docs/`.
- Check the linter for accessibility rules (ESLint's `jsx-a11y`, Biome's `a11y` group) and the test setup for axe.
- Read the root layout once: `<html lang>` and `dir`, a skip link, the landmarks. Fix them there, not per page.

## Semantics before ARIA

- Use the element that already has the behavior: `<button>` for actions, `<a href>` (or the router's `Link`) for navigation, native inputs, `<fieldset>` and `<legend>` for groups. Never a `div` or `span` with `onClick`.
- Add ARIA only for what HTML cannot express, and then the whole pattern: role, states, keyboard. ARIA adds no behavior, and a wrong role is worse than none.
- One `<h1>` per page and headings in order, chosen for structure, not size. Landmarks: `<header>`, a named `<nav aria-label="…">` per navigation, one `<main id="main">`, `<footer>`. The first focusable element is a skip link to `#main`.
- Every page has a unique, descriptive title, set through the framework's metadata.
- Every control has an accessible name: visible text, a `<label>`, or `aria-label` for icon-only controls. When there is visible text, the name contains it (2.5.3).
- Meaningful images get `alt` that says what they convey; decorative ones `alt=""`. Decorative icons get `aria-hidden="true"` (lucide-react adds it unless you pass an `aria-*`, `role` or `title` prop). An icon-only button carries the name itself: `<button aria-label="Close"><XIcon /></button>`.
- Tabular data is a `<table>` with a `<caption>` (or `aria-label`), `<th scope="col">` and `scope="row"`. Sortable headers hold a `<button>`, with `aria-sort` on the `<th>`.

## Forms and errors

- Tie labels to inputs with `useId()`: `<label htmlFor={id}>` and `<input id={id}>`. Never build ids from `Math.random()` or counters: they break hydration.
- A placeholder is not a label. Mark required fields in text, not only with a red asterisk, and with `required`.
- On error: `aria-invalid="true"` on the control, the message's id in its `aria-describedby` (beside any hint), and text that says how to fix it. After a failed submit, focus the first invalid field or an error summary that links to each field.

```tsx
function EmailField({ error }: { error?: string }) {
  const id = useId();
  return (
    <div>
      <label htmlFor={id}>Email</label>
      <input
        aria-describedby={error ? `${id}-error` : undefined}
        aria-invalid={error ? true : undefined}
        autoComplete="email"
        id={id}
        name="email"
        required
        type="email"
      />
      {error ? <p id={`${id}-error`}>{error}</p> : null}
    </div>
  );
}
```

- Results of async work (saved, failed, 12 results) go to a polite live region (`role="status"`) that is already in the DOM.
- Use `autocomplete` tokens on personal data (`name`, `email`, `current-password`, `new-password`, `one-time-code`) (1.3.5). Allow paste and password managers; never require a puzzle or memory test to sign in without an alternative (3.3.8). Don't ask again for what the user entered earlier in the same flow (3.3.7).
- Prefer an enabled submit button that validates and explains over a disabled one that hides the reason.

## Focus and keyboard

- Menus, comboboxes, listboxes, tabs, dialogs, popovers, tooltips, sliders and date pickers come from Base UI, Radix or React Aria primitives, which implement the WAI-ARIA keyboard patterns, focus handling and labelling. Hand-roll one only when none fits, and then implement the whole APG pattern: roving `tabIndex`, arrows, Home and End, Escape, typeahead.
- Dialogs: focus moves in on open (the first field, or the least destructive button in an alert), Tab stays inside, Escape closes, focus returns to the trigger, the page behind is inert, and a visible title names the dialog. Adjust focus through the library (Base UI's `initialFocus` and `finalFocus`, Radix's `onOpenAutoFocus` and `onCloseAutoFocus`), not with timers.
- Use `inert` (a boolean prop in React 19) to remove a closed drawer, an off-screen panel or the background of a custom modal from the tab order and the accessibility tree. Never `aria-hidden` on a container that still holds focusable elements.
- Route changes: the new page must be announced and focus must start at its top. Next.js's route announcer reads `document.title`, so titles must be unique. In routers without one, focus the new page's `<h1 tabIndex={-1}>` after navigation. Never move focus on filter or search-param changes within a page.
- No focus loss on re-render: keep `key`s stable, never define a component inside another (it remounts every render), and don't swap the focused button for a spinner: keep the button and show the pending state inside it. When you remove the focused element (deleting a row), move focus to a neighbor or the list's heading.
- Show focus on everything focusable with `focus-visible:` styles at 3:1 contrast (`focus-visible:outline-2 focus-visible:outline-offset-2`). Remove the default only with Tailwind CSS v4's `outline-hidden`, which keeps an outline in forced-colors mode, and only when you draw your own.
- Sticky headers and cookie banners must not cover the focused element (2.4.11): set `scroll-padding-top` to the header's height.
- Never use a positive `tabIndex`; use `tabIndex={-1}` only on programmatic focus targets.
- Every drag interaction (reorder, resize, a map pan) has a single-pointer or keyboard alternative (2.5.7).

## Visual design

- Text contrast at least 4.5:1, or 3:1 for large text (24px, or 18.66px bold). Control boundaries, focus indicators, meaningful icons and chart lines need 3:1 (1.4.11). Check light and dark themes and every state; placeholder text counts.
- Never convey meaning by color alone: errors carry an icon or text, the selected tab changes weight or gets an underline, links inside a paragraph are underlined.
- Targets are at least 24 by 24 CSS pixels, or spaced so a 24px circle on each doesn't overlap another (2.5.8). Toolbar icon buttons are the usual failure: pad the hit area, not the icon.
- Respect `prefers-reduced-motion` with Tailwind's `motion-safe:` and `motion-reduce:` variants or the animation library's reduced-motion hook. Replace large movement, parallax and auto-scroll with a fade or nothing. Anything that moves for more than 5 seconds gets a pause control (2.2.2).
- Content reflows at 320 CSS pixels wide without horizontal scrolling (tables, code and maps excepted) and survives 200 percent text zoom: size text in `rem`, never fix the height of text containers.
- Support RTL with logical properties: `ms-*`, `me-*`, `ps-*`, `pe-*`, `start-*`, `end-*`, `text-start`, `border-s`. Mirror directional icons with `rtl:-scale-x-100`, set `dir` on `<html>`, and wrap the app in the primitives' direction provider.
- Tooltips and hover cards also open on focus, stay while hovered, and close on Escape (1.4.13). Never put the only copy of essential information in one.

## Live regions and toasts

- A live region must exist before its content changes: render it empty and update its text. `role="status"` for most updates, `role="alert"` only for errors that need attention now.
- Don't announce every keystroke: debounce result counts. Don't announce what focus already reveals.
- Toasts are for brief, non-critical messages. Anything the user must act on goes in the page or a dialog. Use the library's toast (Sonner, Base UI Toast), pause it on hover and focus, and keep it long enough to read.

## Testing

- Component tests: query by role and name with Testing Library (a control you can't find by role is a bug) and run axe on each state: errors shown, menu open, dialog open.
- End-to-end tests: `@axe-core/playwright` with `withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22aa"])` on every key page and open overlay. jsdom can't compute contrast, so the browser run is the one that checks it.
- Automated checks catch only part of the problems. Do a keyboard pass on everything you build, and a screen reader spot check (VoiceOver, NVDA) on new widgets and flows.

## Do not

- Do not put `onClick` on a `div`, `span` or an `<a>` without `href`, or `role="button"` on a link.
- Do not set `aria-label` on a generic `div` or `span` (it is ignored there), or `aria-hidden` on anything focusable.
- Do not use `title` as the only name, or `tabIndex={0}` on static text.
- Do not use `outline-none` without a replacement, or `:focus` styles where `:focus-visible` is meant.
- Do not autoplay media with sound, or use `autoFocus` on page load outside a dialog or a single-purpose page.
- Do not add an overlay "accessibility widget" in place of fixing the markup.

## Before you finish

- Run the linter with accessibility rules and the axe tests, and fix what they report.
- Do a keyboard-only pass on what you changed: every control reachable and operable, focus visible and in a logical order, no traps, Escape closes overlays and focus returns.
- Zoom to 200 percent, narrow to 320 pixels, switch to dark mode and to reduced motion, and check again.
- Say what you could not verify, such as a screen reader pass, so a person does it.

9,647 characters, 1,514 words. Under Windsurf's 12,000-character limit for a rule, as every prompt here is.

Use it in your agent

The same rules, where your agent reads them. Each file has the front matter its agent needs, set to load when the task fits.

Claude Code

A rule it loads every session

  1. Save it as .claude/rules/react-accessibility.md in your project, front matter included, or run the install command.
  2. Its paths front matter loads it when Claude reads or edits a matching file, and keeps it out of other work.
  3. Prefer one file? Paste it into CLAUDE.md under its own heading instead.

.claude/rules/react-accessibility.md

---paths:  - "**/*.tsx"  - "**/*.jsx"---# React AccessibilityYou are building a React 19 app (often Next.js 16 with Tailwind CSS v4 and shadcn-style components on Base UI, Radix or React Aria) that must meet WCAG 2.2 level AA. Treat accessibility as correctness: a control that a keyboard or screen reader user cannot operate is a bug, not a polish item.## Before you write code- Check which primitives the project uses (`@base-ui/react`, `radix-ui`, `react-aria-components`) and its `components/ui/` wrappers. Build on them before writing any custom widget. Base UI ships its docs in `node_modules/@base-ui/react/docs/`.- Check the linter for accessibility rules (ESLint's `jsx-a11y`, Biome's `a11y` group) and the test setup for axe.- Read the root layout once: `<html lang>` and `dir`, a skip link, the landmarks. Fix them there, not per page.## Semantics before ARIA…

Or let the shadcn CLI write this file: sign in (free) for your command.

Cursor

A project rule with front matter

  1. Save it as .cursor/rules/react-accessibility.mdc, front matter included, or run the install command.
  2. Cursor attaches it when you work on matching files, or when the task fits its description.
  3. Check it under Cursor Settings, Rules, then commit the folder for your team.

.cursor/rules/react-accessibility.mdc

---description: "Building or reviewing React UI: semantic HTML, accessible names, form errors, focus, keyboard, contrast, target size and motion for WCAG 2.2 AA."globs: **/*.tsx,**/*.jsxalwaysApply: false---# React AccessibilityYou are building a React 19 app (often Next.js 16 with Tailwind CSS v4 and shadcn-style components on Base UI, Radix or React Aria) that must meet WCAG 2.2 level AA. Treat accessibility as correctness: a control that a keyboard or screen reader user cannot operate is a bug, not a polish item.## Before you write code- Check which primitives the project uses (`@base-ui/react`, `radix-ui`, `react-aria-components`) and its `components/ui/` wrappers. Build on them before writing any custom widget. Base UI ships its docs in `node_modules/@base-ui/react/docs/`.- Check the linter for accessibility rules (ESLint's `jsx-a11y`, Biome's `a11y` group) and the test setup for axe.- Read the root layout once: `<html lang>` and `dir`, a skip link, the landmarks. Fix them there, not per page.## Semantics before ARIA…

Or let the shadcn CLI write this file: sign in (free) for your command.

Windsurf

A workspace rule for Cascade

  1. Save it as .windsurf/rules/react-accessibility.md, or run the install command.
  2. trigger: model_decision lets Cascade pull it in when the task fits its description.
  3. Windsurf reads up to 12,000 characters per rule; this one is 9,647.

.windsurf/rules/react-accessibility.md

---trigger: model_decisiondescription: "Building or reviewing React UI: semantic HTML, accessible names, form errors, focus, keyboard, contrast, target size and motion for WCAG 2.2 AA."---# React AccessibilityYou are building a React 19 app (often Next.js 16 with Tailwind CSS v4 and shadcn-style components on Base UI, Radix or React Aria) that must meet WCAG 2.2 level AA. Treat accessibility as correctness: a control that a keyboard or screen reader user cannot operate is a bug, not a polish item.## Before you write code- Check which primitives the project uses (`@base-ui/react`, `radix-ui`, `react-aria-components`) and its `components/ui/` wrappers. Build on them before writing any custom widget. Base UI ships its docs in `node_modules/@base-ui/react/docs/`.- Check the linter for accessibility rules (ESLint's `jsx-a11y`, Biome's `a11y` group) and the test setup for axe.- Read the root layout once: `<html lang>` and `dir`, a skip link, the landmarks. Fix them there, not per page.## Semantics before ARIA…

Or let the shadcn CLI write this file: sign in (free) for your command.

GitHub Copilot

Instructions for the repository

  1. Save it as .github/instructions/react-accessibility.instructions.md, or run the install command.
  2. applyTo limits it to the files it's about, in chat, the coding agent and code review.
  3. Or paste it into .github/copilot-instructions.md, which Copilot reads for every request.

.github/instructions/react-accessibility.instructions.md

---description: "Building or reviewing React UI: semantic HTML, accessible names, form errors, focus, keyboard, contrast, target size and motion for WCAG 2.2 AA."applyTo: "**/*.tsx,**/*.jsx"---# React AccessibilityYou are building a React 19 app (often Next.js 16 with Tailwind CSS v4 and shadcn-style components on Base UI, Radix or React Aria) that must meet WCAG 2.2 level AA. Treat accessibility as correctness: a control that a keyboard or screen reader user cannot operate is a bug, not a polish item.## Before you write code- Check which primitives the project uses (`@base-ui/react`, `radix-ui`, `react-aria-components`) and its `components/ui/` wrappers. Build on them before writing any custom widget. Base UI ships its docs in `node_modules/@base-ui/react/docs/`.- Check the linter for accessibility rules (ESLint's `jsx-a11y`, Biome's `a11y` group) and the test setup for axe.- Read the root layout once: `<html lang>` and `dir`, a skip link, the landmarks. Fix them there, not per page.## Semantics before ARIA…

Or let the shadcn CLI write this file: sign in (free) for your command.

Codex & AGENTS.md

The file most agents read

  1. Paste it into AGENTS.md at your project's root, under its own heading.
  2. Codex reads AGENTS.md before it starts work, and so do Cursor, Zed, Jules, Amp and Copilot's coding agent.
  3. A folder can have its own AGENTS.md: the one closest to the file being edited wins.

AGENTS.md

## React AccessibilityYou are building a React 19 app (often Next.js 16 with Tailwind CSS v4 and shadcn-style components on Base UI, Radix or React Aria) that must meet WCAG 2.2 level AA. Treat accessibility as correctness: a control that a keyboard or screen reader user cannot operate is a bug, not a polish item.### Before you write code- Check which primitives the project uses (`@base-ui/react`, `radix-ui`, `react-aria-components`) and its `components/ui/` wrappers. Build on them before writing any custom widget. Base UI ships its docs in `node_modules/@base-ui/react/docs/`.- Check the linter for accessibility rules (ESLint's `jsx-a11y`, Biome's `a11y` group) and the test setup for axe.- Read the root layout once: `<html lang>` and `dir`, a skip link, the landmarks. Fix them there, not per page.### Semantics before ARIA…

AGENTS.md is shared with your own instructions, so paste the rules in: the copy sets their headings a level down.

Rules teach it. The MCP equips it. With Pro, your agent installs every component, block and icon it needs, from a prompt.

Get Pro

Pricing

Give your agent the parts, with Pro

The prompts are free. Every Pro plan adds the MCP server, so your agent installs the components, blocks and icons it writes code around.

4.9/5 out of 5 · 150+ reviewsfrom 150+ founders and developers.

Billed once a year in USD. Cancel anytime.

Individual

For personal projects & open source

$32instead of $49/mo

$389 billed yearly

  • How many people can use the license.Seats: 1 seat
  • Client work, SaaS products and anything you charge for.Commercial use: Personal & open source only
  • Page sections in 56 categories, from heroes and pricing tables to dashboards and settings, on every framework and UI library, in your theme.Blocks: All 6,181 blocks
  • Complete Next.js sites for startups, SaaS, agencies and portfolios, every page designed, ready to ship.Templates: All 14 templates
  • Your AI agent searches, previews and installs any component, block, icon or DESIGN.md from a prompt: 22 tools in Claude Code, Cursor, VS Code and 35 more clients.MCP server: 38 AI clients
  • Buttons with motion and states of their own, from an arrow that slides in to a burst of confetti.Animated buttons: All 35 buttons
  • Typewriters, scrambles, counters and more, for headlines, labels and numbers.Text effects: All 97 effects
  • Animated backgrounds for heroes, sections and cards, from a soft aurora to a flock that flees the cursor. Most are pure CSS.Backgrounds: All 100 backgrounds
  • WebGPU shaders, from a slow aurora to a black hole, each drawing only while it's on screen.Shaders: All 100 shaders
  • One design and one theme on all four: the components, blocks and effects are built for each framework.Frameworks: React, Vue, Svelte, Solid
  • React on Base UI, Radix UI, React Aria or Ark UI: install the version for the library your project already uses. Vue, Svelte and Solid run on Ark UI.UI libraries: Base UI, Radix, Aria, Ark
  • From a button to a date picker, a color picker or a guided tour: accessible, typed in TypeScript, each with live demos.Components: All 174 components
  • Real-world patterns built from the components, in 53 galleries: forms, tables, menus, dialogs and more.Examples: All 1,103 examples
  • Area, bar, line, pie, radar and radial charts on TanStack Charts, on every framework. React also gets the Recharts originals.Charts: All 70 charts
  • Debounce, local storage, media queries and more: each hook is one typed file you own.React hooks: All 35 hooks
  • From 222 libraries, Lucide, Heroicons, Tabler and Material Symbols among them, as SVG or a React component in one command.Icons: 294k+ icons
  • Stripe, Linear, Apple and 497 more: a brand's colors, typography and components in one file your AI agent builds from.DESIGN.md files: All 500 brands
  • Restyle every component and block at once: 8 styles, 7 grays, 18 colors and 5 radii, shared as a shadcn preset code.Theme builder: 8 styles × 18 colors
  • Priority requests are answered first.Support: Standard

Team

Most popular

For freelancers, agencies & startups

$66instead of $99/mo

$789 billed yearly · ≈ $13/dev/mo

Everything in Individual, plus

  • How many people can use the license.Seats: Up to 5 seats
  • Client work, SaaS products and anything you charge for.Commercial use: Included
  • Priority requests are answered first.Support: Priority

Organization

For companies with larger teams

$132instead of $199/mo

$1,589 billed yearly

Everything in Team, plus

  • How many people can use the license.Seats: Unlimited seats
  • Secure checkout via Stripe
  • Code lives in your repo
  • Keep what you've shipped if you cancel
  • License terms

React Accessibility prompt FAQ

What the React Accessibility prompt covers and how to use it in your agent.

Should I build accessible components with Base UI, Radix or React Aria?

Any of them: the prompt has the agent use whichever the project already has instead of hand-rolling menus, comboboxes, tabs or dialogs. Each implements the WAI-ARIA keyboard patterns and focus handling; the agent still owns labels, visible focus and contrast.

How does the React Accessibility prompt manage focus in dialogs and route changes?

Dialogs move focus in, keep Tab inside, close on Escape and return focus to the trigger, configured through the library's focus props. On route changes it relies on unique page titles for the Next.js route announcer, and focuses the new h1 in routers without one.

Why does focus jump to the top of the page after a React re-render?

Usually because the focused element was unmounted: a component defined inside another, an unstable key, or a button swapped for a spinner. The prompt forbids those patterns and has the agent move focus to a neighbor when it deletes the focused item.

How does the React Accessibility prompt treat icons and alt text?

Meaningful images get alt text that says what they convey, and decorative ones an empty alt. Decorative icons are aria-hidden, which lucide-react does by default, and an icon-only button carries its own aria-label.

4.9 out of 5 from 150+ developers

Rules teach it. The MCP equips it.

Every component, block and icon, installed by your AI agent on your stack. The prompts stay free.

Get Pro
  • Cancel anytime
  • Keep what you've shipped