oxc-project/oxc · warning

Interactive elements should not be assigned non-interactive

Error message

Interactive elements should not be assigned non-interactive roles.

What it means

This is the `jsx_a11y/no-interactive-element-to-noninteractive-role` rule in oxlint. It fires when a natively interactive HTML element (per `is_interactive_element`, e.g. `<a href>`, `<button>`, `<input>`) is given a non-interactive ARIA role (per `is_non_interactive_role`, e.g. `listitem`, `heading`, `presentation`). Converting an interactive element to a non-interactive one removes expected semantics while it still behaves interactively.

Source

Thrown at crates/oxc_linter/src/rules/jsx_a11y/no_interactive_element_to_noninteractive_role.rs:22

use oxc_ast::{AstKind, ast::JSXAttributeValue};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use oxc_str::CompactStr;

use crate::{
    AstNode,
    context::LintContext,
    globals::HTML_TAG,
    rule::{DefaultRuleConfig, Rule},
    utils::{
        get_element_type, has_jsx_prop_ignore_case, is_interactive_element, is_non_interactive_role,
    },
};

fn no_interactive_element_to_noninteractive_role_diagnostic(span: Span) -> OxcDiagnostic {
    OxcDiagnostic::warn("Interactive elements should not be assigned non-interactive roles.")
        .with_help("WAI-ARIA roles should not be used to convert an interactive element to a non-interactive element. Wrap the element or use a different structure.")
        .with_label(span)
}

#[derive(Debug, Default, Clone, Deserialize)]
pub struct NoInteractiveElementToNoninteractiveRole(
    Box<NoInteractiveElementToNoninteractiveRoleConfig>,
);

#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct NoInteractiveElementToNoninteractiveRoleConfig {
    /// A mapping of HTML element names to arrays of ARIA role strings that are
    /// allowed overrides for that element.
    #[serde(default, flatten)]
    pub allowed_roles: FxHashMap<CompactStr, Vec<CompactStr>>,
}

impl Default for NoInteractiveElementToNoninteractiveRoleConfig {

View on GitHub (pinned to e1e7af627c)

Solutions

  1. Remove the non-interactive `role` from the interactive element.
  2. Restructure: wrap the interactive element in a container that carries the non-interactive role.
  3. If the pairing is intentional, whitelist it in the rule config's per-element allowed roles mapping.

Example fix

// before
<a href="#" role="listitem" onClick={select}>Item</a>

// after
<li><a href="#" onClick={select}>Item</a></li>
Defensive patterns

Strategy: validation

Validate before calling

npx oxlint --jsx_a11y/no-interactive-element-to-noninteractive-role src/

Prevention

When it happens

Trigger: A JSX element resolved as an interactive HTML tag that also has a `role` attribute (found via `has_jsx_prop_ignore_case`) whose literal value is a non-interactive role, and the pair is not whitelisted in the rule's configurable `roles` mapping (HTML element name -> allowed role list).

Common situations: `<a href="#" role="listitem">` inside custom lists; `<button role="presentation">` used to hide a control; CSS-framework markup that sprinkles roles on buttons/links; refactors that changed element types without updating roles.

Related errors


AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20). Data as JSON: /api/errors/099d965609a401bd. Report an issue: GitHub.