Interactive Role Needs Focus

ID

javascript.jsx_interactive_role_needs_focus

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

JavaScript

Tags

accessibility, jsx, reliability

Description

Reports a JSX element that declares an interactive ARIA role and handles activation, yet cannot receive keyboard focus, because it is neither natively focusable nor given a tabIndex.

Rationale

An interactive role plus a handler is a promise that the element behaves like a control, and the first thing a control has to do is take focus. Without it the element is announced as a button to a screen reader user who can never reach it: the Tab key skips it, so does the screen reader’s own control navigation, and the only way to activate it is a pointer. This is the most common way a custom control ends up mouse-only, and it is invisible in testing because the mouse path works perfectly.

<div role="button" onClick={save}>Save</div>              // FLAW - mouse only
<li role="option" onClick={pick}>One</li>                 // FLAW - same
<a role="link" onKeyDown={go}>Home</a>                    // FLAW - no href, so no focus

<div role="button" onClick={save} tabIndex={0}>Save</div> // OK
<button role="button" onClick={save}>Save</button>        // OK - focusable already
<span role="checkbox" aria-checked="false" />             // OK - no handler yet
<div role="button" onMouseEnter={preview}>Save</div>      // OK - hover is not activation
<ul role="listbox">...</ul>                               // OK - a composite container
<div role="button" onClick={save} {...rest} />            // OK - attributes may be spread in

The handler is required, and that is a deliberate narrowing. A role on its own is a claim; a role with a handler is a claim the code has started keeping. An element with an interactive role but neither handler nor tabIndex is far more often decoration, a placeholder or half-built markup than a broken control, so it is left alone. The handlers that count are the activation ones — onClick, onKeyDown, onKeyUp, onKeyPress, onMouseDown and onMouseUp; a hover or focus handler observes the pointer rather than acting on it.

A natively focusable element needs nothing further. Everything else has to opt in with a tabIndex, and its presence is enough: 0, -1 or computed, the author has thought about focus. The roles reported are the ones whose own element is the focus target; a composite widget role such as listbox, menu or tablist manages focus for its children rather than taking it, so a container declaring one is correct as written.

Remediation

Add tabIndex={0} so the element joins the tab order at its DOM position, and handle Enter and Space (or the arrow keys, for a widget item) alongside the click. Where the element is one item of a composite widget, follow that widget’s focus pattern instead: one tab stop on the container, arrow keys between items. Where the markup is a plain control, a button or an a with an href brings focus, key handling and the right announcement for free.

© 2026 Xygeni. All rights reserved