Skip to main content
React Doctor includes 15 accessibility rules from the jsx-a11y plugin to help you build interfaces that work for everyone.

Why Accessibility Matters

  • 15% of the world has some form of disability
  • Legal requirement in many jurisdictions (ADA, Section 508, WCAG)
  • Better UX for everyone (keyboard nav, screen readers, low vision)
  • SEO benefits from semantic HTML

Rules

Severity: error
Rule ID: jsx-a11y/alt-text
Requires alt attribute on <img> elements. Screen readers use alt text to describe images.Bad:
<img src="logo.png" />
Good:
<img src="logo.png" alt="Company logo" />

// Decorative images should have empty alt
<img src="divider.png" alt="" />
Severity: warn
Rule ID: jsx-a11y/anchor-is-valid
Ensures <a> elements have valid href attributes. Links without hrefs should be buttons.Bad:
<a onClick={handleClick}>Click me</a>
<a href="#">Click me</a>
Good:
<button onClick={handleClick}>Click me</button>
<a href="/page">Go to page</a>
Severity: warn
Rule ID: jsx-a11y/click-events-have-key-events
Requires keyboard event handlers alongside click handlers for accessibility.Bad:
<div onClick={handleClick}>Click me</div>
Good:
// Use semantic button instead
<button onClick={handleClick}>Click me</button>

// Or add keyboard handler
<div
  onClick={handleClick}
  onKeyDown={(e) => e.key === 'Enter' && handleClick()}
  role="button"
  tabIndex={0}
>
  Click me
</div>
Severity: warn
Rule ID: jsx-a11y/no-static-element-interactions
Prevents adding click/keyboard handlers to non-interactive elements without proper ARIA roles.Bad:
<div onClick={handleClick}>Clickable</div>
Good:
<button onClick={handleClick}>Clickable</button>

// Or with role
<div role="button" tabIndex={0} onClick={handleClick}>Clickable</div>
Severity: warn
Rule ID: jsx-a11y/no-noninteractive-element-interactions
Prevents adding interactions to non-interactive elements like <li>, <article>, etc.Bad:
<li onClick={handleClick}>Item</li>
Good:
<li>
  <button onClick={handleClick}>Item</button>
</li>
Severity: error
Rule ID: jsx-a11y/role-has-required-aria-props
Ensures ARIA roles have all required ARIA attributes.Bad:
<div role="checkbox" />
Good:
<div role="checkbox" aria-checked="false" />
Severity: warn
Rule ID: jsx-a11y/no-autofocus
Discourages autoFocus as it can be disorienting for screen reader users.Bad:
<input autoFocus />
Good:
// Let users focus naturally, or use programmatic focus
const inputRef = useRef();
useEffect(() => {
  inputRef.current?.focus();
}, []);
Severity: warn
Rule ID: jsx-a11y/heading-has-content
Requires heading elements to have content for screen readers.Bad:
<h1></h1>
<h2 />
Good:
<h1>Page Title</h1>
<h2>{dynamicTitle}</h2>
Severity: warn
Rule ID: jsx-a11y/html-has-lang
Requires <html> element to have lang attribute for screen readers.Bad:
<html>
Good:
<html lang="en">
Severity: warn
Rule ID: jsx-a11y/no-redundant-roles
Prevents specifying redundant ARIA roles that are implicit.Bad:
<button role="button">Click</button>
<nav role="navigation">...</nav>
Good:
<button>Click</button>
<nav>...</nav>
Severity: warn
Rule ID: jsx-a11y/scope
Ensures scope attribute is only used on <th> elements in tables.Bad:
<td scope="col">Header</td>
Good:
<th scope="col">Header</th>
Severity: warn
Rule ID: jsx-a11y/tabindex-no-positive
Prevents positive tabIndex values which disrupt natural tab order.Bad:
<div tabIndex={1}>First</div>
<div tabIndex={2}>Second</div>
Good:
<div tabIndex={0}>Focusable</div>
<div tabIndex={-1}>Not in tab order</div>
Severity: warn
Rule ID: jsx-a11y/label-has-associated-control
Requires <label> elements to be associated with form controls.Bad:
<label>Email</label>
<input type="email" />
Good:
// htmlFor
<label htmlFor="email">Email</label>
<input id="email" type="email" />

// Nested
<label>
  Email
  <input type="email" />
</label>
Severity: error
Rule ID: jsx-a11y/no-distracting-elements
Prevents using distracting elements like <marquee> and <blink>.Bad:
<marquee>Scrolling text</marquee>
<blink>Blinking text</blink>
Good:
<div className="animate-slide">Scrolling text</div>
Severity: warn
Rule ID: jsx-a11y/iframe-has-title
Requires <iframe> elements to have a title attribute.Bad:
<iframe src="/embed" />
Good:
<iframe src="/embed" title="Embedded content" />

Accessibility Best Practices

Semantic HTML

Use the right element for the job:
// ✅ Semantic
<button onClick={handleClick}>Submit</button>
<nav><a href="/">Home</a></nav>
<main><article>Content</article></main>

// ❌ Non-semantic
<div onClick={handleClick}>Submit</div>
<div><div onClick={nav}>Home</div></div>
<div><div>Content</div></div>

Keyboard Navigation

Ensure all interactive elements are keyboard accessible:
  • Use semantic HTML (<button>, <a>, <input>)
  • Add tabIndex={0} for custom interactive elements
  • Handle Enter and Space keys for custom controls
  • Provide visible focus indicators

Screen Readers

Make content accessible to screen readers:
  • Use alt text for images
  • Label form inputs
  • Use ARIA labels when needed: aria-label, aria-labelledby
  • Provide aria-live regions for dynamic content
  • Use proper heading hierarchy (h1h2h3)

Color and Contrast

  • Maintain 4.5:1 contrast ratio for text
  • Don’t rely on color alone to convey information
  • Test with color blindness simulators

Testing Tools

Build docs developers (and LLMs) love