Interactive Role On A Structural Element

ID

html.non_interactive_element_interactive_role

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

Html

Tags

accessibility, reliability

Description

Flags an interactive ARIA role on an element whose tag already carries a structural role.

Rationale

Overwriting a structural role with a widget role trades one wrong announcement for another. The element loses the semantics its tag gave it — an li stops being a list item, so the enclosing list stops reporting its size and position — and it gains a control’s semantics without a control’s behaviour: no focus, no key handling, no state. Screen reader users are told there is a button and find nothing that acts like one.

<h2 role="tab">Details</h2>                       <!-- FLAW — a heading is not a tab -->
<p role="textbox">Type here</p>                   <!-- FLAW — a paragraph is not an input -->
<li role="button" onclick="save()">Save</li>      <!-- FLAW — a list item is not a control -->

<div role="button" tabindex="0">Save</div>        <!-- OK — div has no role to overwrite -->
<button onclick="save()">Save</button>            <!-- OK — the element for the job -->
<li role="option">One</li>                        <!-- OK — a listbox item is built this way -->
<ul role="listbox">...</ul>                       <!-- OK — a composite widget container -->
<p role="${kind}">Save</p>                        <!-- OK — settled at render time -->

div and span are deliberately not judged: they carry no implicit role, so a widget role on them overwrites nothing, and that is the standard way to build a custom control. The table row and cell tags are not judged either — inside a grid or treegrid their interactive roles are exactly right.

The roles reported are the ones whose own element is the focus target: button, checkbox, menuitem, tab and their kin. A composite widget role (grid, listbox, tablist) manages focus for its children rather than taking it, so it is not reported. One pairing is exempt as well: an li carrying an item role of a composite widget (option, menuitem, tab, treeitem), which is the published way to build the items of a listbox, a menu or a tree.

Only a role written as plain HTML with a literal value is judged. A directive such as th:role, a template expression, a valueless role and a namespaced element are all left alone.

Remediation

Use the HTML element that already has the behaviour the role claims — a button for role="button", an input for role="textbox" — and keep the structural element around it if the document needs it. When a custom control really is required, build it on a div or a span, which have no semantics to lose, and give it a tabindex and key handling to match the role.

<h2><button onclick="open()">Details</button></h2>
<div role="button" tabindex="0" onclick="save()" onkeydown="onKey(event)">Save</div>

© 2026 Xygeni. All rights reserved