Skip to content
Skill · Cursor.cursor/rules/*.mdcCoding

Accessibility Rules for JSX

A11y rules putting semantic HTML before ARIA, with concrete requirements for focus management, labels, and color contrast.

Use when: Attach to TSX files in any React codebase to catch div-button soup, focus traps, and ARIA misuse while components are being written.

@libraryNewcomer
0 upvotes0 forks0 comments

The skill file

Cursor · .cursor/rules/*.mdc
.cursor/rules/*.mdc3800 chars
--- description: Accessibility requirements for React/JSX components globs: "**/*.tsx" alwaysApply: false --- # Accessibility rules ## Semantic elements first - Interactive things use interactive elements: `<button>` for actions, `<a href>` for navigation. A `<div onClick>` is a defect — it has no keyboard activation, no focus, no role. If a design demands a clickable card, wrap or overlay a real `<button>`/`<a>` inside it. - Choose the element by behavior: navigates to a URL → `<a>`; submits or triggers → `<button type="button">` (always set `type` — the default `submit` causes ghost form submissions). - Structure with landmarks: one `<main>` per page, `<nav>` for nav, `<header>`/`<footer>`, `<section>` only with an accessible name. Heading levels descend without skipping; never pick a heading tag for its font size — style it instead. - Lists of things are `<ul>/<li>`; data grids are `<table>` with `<th scope>`. Recreating either out of divs removes structure screen readers navigate by. ## ARIA only as a last resort - The order of preference is: native element → native attribute → ARIA. `aria-disabled` on a div-button when `<button disabled>` exists is wrong twice. - No redundant roles: `role="button"` on `<button>`, `role="navigation"` on `<nav>` — delete them. - Acceptable ARIA in this codebase: `aria-label` on icon-only buttons, `aria-expanded`/`aria-controls` on disclosure triggers, `aria-current="page"` on active nav links, `aria-live="polite"` regions for async status, `aria-describedby` linking inputs to error text. - If you reach for `role="dialog"`, `role="listbox"`, `role="tablist"`, or similar composite widgets, stop — use the project's existing primitives (Radix/shadcn/react-aria) instead of hand-implementing the keyboard contract. - Decorative images/icons: `alt=""` or `aria-hidden="true"`. Informative images: `alt` describing meaning, not appearance ("Delete invoice", not "trash icon"). ## Focus management - Every interactive element shows a visible focus ring via `focus-visible:` styles. Never `outline-none` without an equal-or-better replacement in the same class list. - Dialogs/drawers/popovers: focus moves into the surface on open, is trapped while open, Escape closes, and focus returns to the trigger on close. With Radix/shadcn primitives this is built in — do not re-implement, and do not break it with `autoFocus` on arbitrary children. - After client-side route changes, move focus to the new page's `<h1>` (tabIndex={-1}) or skip-link target — otherwise keyboard users are stranded at the old position. - After destructive actions that remove the focused element (delete row), explicitly move focus to a sensible neighbor or the list container. - DOM order = visual order. No positive `tabIndex`, ever; `tabIndex={-1}` only for programmatic focus targets. Do not reorder visually with CSS (`order`, `flex-row-reverse`) in ways that contradict tab order. ## Forms and labels - Every input has a `<label htmlFor>` (or wrapping label). Placeholder is never the only label — it vanishes on input. - Errors: set `aria-invalid` on the field, render the message in an element referenced by `aria-describedby`, and move focus to the first invalid field on failed submit. - Group related radios/checkboxes in `<fieldset>` with a `<legend>`. ## Contrast and content - Text contrast ≥ 4.5:1 (3:1 for ≥ 24px text); UI control boundaries and focus indicators ≥ 3:1. Use the design tokens — `text-muted-foreground` on `bg-background` passes; one-off opacity tweaks (`text-foreground/40`) usually fail and need checking. - Never encode meaning in color alone: pair status colors with an icon or text ("Failed", not just red). - Respect `prefers-reduced-motion`: gate non-essential animation behind the media query (`motion-safe:` variants).

Install

drop into your repo

Save this to your project or home directory so Cursor can load it.

path./.cursor/rules/accessibility-jsx-rules.mdc
Discussion

What people are saying

Forks

0 forks of this skill
Loading forks…