How to Make Clickable Elements Accessible in React

Make clickable elements accessible in React by using real buttons and links instead of divs with onClick. Native elements bring focus, keyboard, and roles for free.

5 min read

To make clickable elements accessible in React, use a real button or link instead of a div with an onClick handler. Native elements ship with keyboard support, focus, and a role, so keyboard and screen reader users get the same behavior without extra code.

Prefer a real button

A div with an onClick handler looks clickable but is not reachable by keyboard and has no role for assistive technology.

App.jsxApp.jsx
<div onClick={handleSave}>Save</div>

Swap it for a button and the browser fills in the gaps. The button is focusable, activates on Space and Enter, and is announced as a button.

App.jsxApp.jsx
<button onClick={handleSave}>Save</button>

A div would need a role, a tabIndex, and a key handler just to match what the plain button already does. Reaching for those attributes is usually a sign the wrong element was chosen.

Space and Enter come for free

Native buttons activate with Space and Enter, and mouse clicks already work. A div requires you to re-implement all three interactions, which is easy to get wrong and easy to forget when the component grows. Start from the button and you inherit the correct behavior.

Give it an accessible name

An icon-only button has no text, so a screen reader cannot announce its purpose. Add an aria-label that describes the action.

App.jsxApp.jsx
<button aria-label="Close dialog" onClick={handleClose}>X</button>

The label replaces the letter X in the accessible name, so assistive technology reads "Close dialog button". Visible text is usually clearer than an aria-label, but an icon-only control needs the attribute to stay meaningful.

A link navigates somewhere, and a button performs an action. Use an anchor for navigation and a button for submitting, opening a dialog, or deleting. Styling one to look like the other does not change what it does, and the wrong role misleads keyboard and screen reader users.

Toggle and disabled states

A toggle button announces its on or off state with aria-pressed.

App.jsxApp.jsx
function MuteButton() {
  const [muted, setMuted] = useState(false);
  return (
    <button aria-pressed={muted} onClick={() => setMuted(!muted)}>
      Mute
    </button>
  );
}

The label stays "Mute" whether pressed or not, and the state is exposed separately. A button that is temporarily unavailable should use the disabled attribute so it leaves the tab order, not just a dimmed style.

Do not hide the focus outline

A visible focus ring tells keyboard users where they are. Removing it without an equally visible replacement makes the page unusable by keyboard. Style the outline to match the design, but keep it visible.

What to learn next

For custom controls that must handle their own keys, handling keyboard events covers onKeyDown. Larger widgets need keyboard navigation patterns to move focus inside them.

Rune AI

Rune AI

Key Insights

  • Prefer a button over a div with onClick.
  • Buttons activate with Space and Enter for free.
  • Use links for navigation, buttons for actions.
  • Label icon-only buttons with aria-label.
  • Use aria-pressed for toggles and disabled for inactive buttons.
RunePowered by Rune AI

Frequently Asked Questions

Why is a div with onClick not accessible?

A div is not keyboard focusable and has no button role, so keyboard users cannot reach it and screen readers do not announce it as a control.

What should I use instead of a clickable div?

Use a button for actions and a link for navigation. Both are focusable, activate with the keyboard, and carry the correct role automatically.

How do I label an icon-only button?

Add an aria-label that describes the action, or keep visible text next to the icon. The accessible name comes from text content, aria-label, or aria-labelledby.

Conclusion

Use a real button for actions and a real link for navigation. Add an accessible name to icon-only controls, and use aria-pressed and disabled for state. Native elements already do the hard part.