Key takeaways

  • Prefer native semantic HTML before adding ARIA roles.
  • Every interaction must work with a keyboard and expose a clear accessible name.
  • Static JSX checks catch patterns, not the complete rendered experience.
  • Combine linting with keyboard, screen-reader, zoom, and automated browser checks.

Accessible JSX begins with the HTML that React ultimately renders. A div with an onClick handler may look like a button, but it does not automatically receive keyboard behavior, focus, role, or state semantics. A native button already provides those foundations and is usually the more robust choice.

The React Accessibility Checker can identify supported source patterns in a snippet. It cannot see every prop spread, component implementation, CSS effect, portal, runtime label, or focus transition. Treat it as the first layer in a testing stack, followed by the project linter, browser automation, keyboard use, and assistive-technology review.

Start with native elements

Use button for actions, a for navigation, input/select/textarea for form controls, headings for document structure, and landmark elements where they fit. Native elements expose semantics through the browser accessibility tree and include well-tested keyboard interaction.

ARIA can add names, descriptions, relationships, states, and specialized widget roles, but it does not create behavior. A div role="button" still needs focusability, Enter and Space handling, disabled behavior, and visible focus. Reimplementing a native control increases the number of requirements you must get right.

Component libraries should preserve this principle. A Button component should render a real button by default, forward an appropriate ref, preserve type="button" where accidental form submission is possible, and expose disabled semantics. Visual consistency should not erase HTML meaning.

Accessible names are the interface

Every interactive control needs a name that describes its purpose. Visible text usually provides the best name. Icon-only buttons need a textual accessible name, often through aria-label or visually hidden text. Avoid labels such as “click here” or repeated “more” links without context.

Form controls should have programmatically associated labels. Placeholder text is not a replacement: it disappears when users type, often has low contrast, and may not be announced as reliably. The W3C forms guidance recommends label elements and fieldset/legend for related controls.

Images need alternative text based on purpose. Informative images communicate equivalent content in alt. Decorative images use empty alt so screen readers can ignore them. Linked images need an alternative that describes the link’s destination or action, not merely the pixels.

Example: fix an icon action

// Problem: mouse-only, no name, no button semantics
<div className="close" onClick={onClose}>
  <CloseIcon />
</div>

// Better: native behavior and an explicit accessible name
<button type="button" className="close" onClick={onClose} aria-label="Close dialog">
  <CloseIcon aria-hidden="true" />
</button>

The improved JSX solves only part of a dialog. Opening the dialog should move focus appropriately, focus should remain within a modal interaction, Escape behavior should be deliberate, the dialog needs a name, and closing should return focus to the triggering control. Accessibility follows the complete interaction, not one element.

A layered review workflow

  1. Run static JSX rules. Catch missing alt, unlabeled controls, invalid ARIA attributes, non-interactive elements with handlers, and other supported patterns.
  2. Inspect rendered semantics. Use browser developer tools to examine roles, names, states, and heading/landmark structure after components and props resolve.
  3. Use only the keyboard. Reach every action, operate it with expected keys, follow visible focus, and confirm no region traps focus.
  4. Test focus transitions. Open and close dialogs, submit invalid forms, change routes, remove focused items, and load asynchronous content.
  5. Check zoom and reflow. At increased text size and narrow widths, content should remain available without overlapping or clipping essential controls.
  6. Run automated browser checks. They catch many deterministic violations in the rendered page but cannot judge every label or workflow.
  7. Use a screen reader on key flows. Listen for control names, state changes, validation messages, landmarks, and reading order.
  8. Include disabled and error states. Accessibility bugs often appear only after validation, loading, or permission changes.

Keyboard behavior and focus

All mouse functionality should be available from a keyboard. Native links and buttons already participate in tab order and activation. Do not add positive tabindex values to force an order; DOM order should normally match the visual and logical reading sequence.

Visible focus is essential. Removing outlines without an equally clear replacement makes the interface unusable for keyboard users. Focus indicators need sufficient contrast against all states, including dark backgrounds and selected rows.

When content updates, decide whether focus should move, remain, or be restored. A validation error may need a summary linked to fields. A removed list item may require focus on the next meaningful control. A route change should provide a predictable page title and heading. Avoid moving focus merely to announce passive content; live regions may be appropriate for concise status updates.

Static analysis limitations

Source rules may not know what a custom component renders. A component named Link could produce an anchor, button, or div. Prop spreading can hide event handlers and ARIA attributes. Conditional rendering can create duplicate IDs only in a particular state. CSS can reverse visual order, hide focus, or remove content from display.

Static tools also cannot decide whether an alternative text is useful, a heading describes its section, or an error message is understandable. They can report that a name exists, not that the name communicates the right action. Human review remains necessary.

Design systems improve coverage by solving semantics once in shared primitives. Test those primitives rigorously and document their intended use. Still allow product teams to provide context-specific labels and descriptions; a technically accessible primitive can be misused.

Common mistakes

  • Adding role="button" to a div instead of using button.
  • Providing onClick without keyboard operation or focusability.
  • Using placeholder text as the only form label.
  • Adding alt text that repeats nearby captions or says “image of.”
  • Hiding focus outlines for aesthetic reasons.
  • Using aria-label that conflicts with visible control text.
  • Announcing every asynchronous update and overwhelming screen-reader users.
  • Treating a zero-error automated scan as proof of an accessible experience.

Review checklist

  • Interactive elements use native semantics wherever possible.
  • Every control has a concise, contextual accessible name.
  • Forms use labels, instructions, grouped controls, and connected errors.
  • Images have purpose-appropriate alternative text.
  • All actions work by keyboard with visible focus.
  • Dialogs, menus, and disclosures follow established interaction patterns.
  • DOM order matches reading and focus order.
  • Loading, empty, disabled, success, and error states are tested.
  • Key flows are reviewed at zoom and with a screen reader.

Frequently asked questions

Is ARIA bad?

No. ARIA is essential for states, relationships, and widgets HTML cannot fully express. The principle is to use native semantics first and add ARIA when it provides missing information, not to replace a reliable native control unnecessarily.

Does an icon need alt text?

An SVG icon inside a named button is often decorative and can be hidden from assistive technology because the button supplies the name. A standalone informative graphic needs an equivalent accessible description appropriate to its context.

Can automated testing guarantee WCAG conformance?

No. Automated tools can reliably detect a useful subset of failures. They cannot judge every meaning, workflow, cognitive burden, or assistive-technology experience. Combine automation with manual and user-centered testing.

What should I test first on a large application?

Prioritize the shared component library and the highest-value user journeys: sign-in, navigation, search, forms, checkout, account management, and error recovery. Fixing a shared primitive can improve many routes at once.

Further reading

Accessible JSX is not a special layer added at the end. It is the result of choosing semantic elements, designing complete keyboard and focus behavior, and verifying the rendered experience in realistic states.

Try the related tool

Apply the guide to a focused example, then verify the result in your own project.

Open the related tool →