Skip to content

Focusable elements need a role that fits

  • Rule: axe/focus-order-semantics
  • Checks: Elements in the focus order should have an appropriate role
  • WCAG: none; a best practice, not a WCAG success criterion
  • Fix class: mechanical: A fix needs no authored text or visual decision.
  • People who use a screen reader or a braille display, most of them blind.
  • People who use a keyboard or a switch device instead of a mouse.

A <div> or <span> that acts as a control has tabindex="0"

Section titled “A <div> or <span> that acts as a control has tabindex="0"”

Use a native <button>, or <a href> if it navigates, which brings focus, keyboard activation and the role with it. If the element must stay custom, give it the matching role, such as role="button", and handle Enter and Space.

<button type="button" class="filter-toggle">Show filters</button>

A heading or paragraph has tabindex="0" so script can move focus to it

Section titled “A heading or paragraph has tabindex="0" so script can move focus to it”

Use tabindex="-1". Script can still focus it, such as after a route change, without adding a stop to the tab order.

<h1 tabindex="-1">Search results</h1>

A scrollable <div> has tabindex="0" so keyboard users can scroll it

Section titled “A scrollable <div> has tabindex="0" so keyboard users can scroll it”

Keep the tabindex, and give it role="region" and a name, with aria-label or aria-labelledby, that says what it holds.

<div class="log" tabindex="0" role="region" aria-label="Build log">
<pre>Build finished in 12 seconds</pre>
</div>

Adding role="button" without keyboard handling

Section titled “Adding role="button" without keyboard handling”

Screen readers announce a button, but Enter and Space do nothing until script handles them, which a native <button> does for free.

Removing tabindex from an element people need to operate

Section titled “Removing tabindex from an element people need to operate”

The rule passes, but keyboard users can no longer reach the control at all.

The part of the fix that differs in React, in its syntax. The rest of the guidance holds.

An effect focuses the page heading after a route change, so the heading has tabIndex={0}

Section titled “An effect focuses the page heading after a route change, so the heading has tabIndex={0}”

Use tabIndex={-1}. Calling focus() on the ref still works, and the heading adds no stop to the tab order.

function PageHeading({ children }) {
const ref = useRef(null);
useEffect(() => {
ref.current.focus();
}, []);
return (
<h1 ref={ref} tabIndex={-1}>
{children}
</h1>
);
}

Adding tabIndex={0} and onKeyDown to a clickable <div> to satisfy jsx-a11y

Section titled “Adding tabIndex={0} and onKeyDown to a clickable <div> to satisfy jsx-a11y”

That quiets click-events-have-key-events, but the element still has no role, so no-static-element-interactions and this rule still fail and screen readers announce no control. Render a <button type="button"> and give it the onClick.

axe-core’s documentation of the rule

CORA’s fix guidance adapts content from accessibility-agents, under the MIT License: see its notice.