oxc-project/oxc · warning

Non-interactive elements should not be assigned interactive

Error message

Non-interactive elements should not be assigned interactive roles.

What it means

This is the `jsx_a11y/no-noninteractive-element-to-interactive-role` rule in oxlint. It fires when a non-interactive HTML element (per `is_non_interactive_element`) is given an interactive ARIA role (per `is_interactive_role`), e.g. `<ol role="listbox">` or `<table role="grid">`. The mismatch breaks native semantics and expected keyboard behavior that the interactive role promises.

Source

Thrown at crates/oxc_linter/src/rules/jsx_a11y/no_noninteractive_element_to_interactive_role.rs:24

use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use oxc_str::CompactStr;
use rustc_hash::FxHashMap;
use schemars::JsonSchema;
use serde::Deserialize;

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

fn no_noninteractive_element_to_interactive_role_diagnostic(span: Span) -> OxcDiagnostic {
    OxcDiagnostic::warn("Non-interactive elements should not be assigned interactive roles.")
        .with_help("Remove the interactive role or use an appropriate interactive element instead.")
        .with_label(span)
}

/// Default allowed overrides matching the `eslint-plugin-jsx-a11y` recommended config.
fn default_allowed_roles() -> FxHashMap<CompactStr, Vec<CompactStr>> {
    let mut map = FxHashMap::default();
    map.insert(
        CompactStr::new("ul"),
        vec![
            CompactStr::new("menu"),
            CompactStr::new("menubar"),
            CompactStr::new("radiogroup"),
            CompactStr::new("tablist"),
            CompactStr::new("tree"),
            CompactStr::new("treegrid"),
        ],
    );

View on GitHub (pinned to e1e7af627c)

Solutions

  1. Use the semantically correct native element (`<select>`, `<button>`, real menu markup) instead of a role override.
  2. If the role override is a sanctioned pattern (like `ul` -> `menu`), confirm it is in the allowed mapping or add it to the rule's config.
  3. Restructure so the interactive role lands on an element that also provides full keyboard support.

Example fix

// before
<ol role="listbox" value={v} onChange={c}>...</ol>

// after
<select value={v} onChange={c}>...</select>
Defensive patterns

Strategy: validation

Validate before calling

npx oxlint --jsx-a11y/no-noninteractive-element-to-interactive-role src/

Prevention

When it happens

Trigger: A non-interactive element carries a `role` whose literal value resolves to an interactive role, and the (element, role) pair is not whitelisted. Defaults (mirroring eslint-plugin-jsx-a11y, see `default_allowed_roles`) allow e.g. `ul` -> `menu`/`menubar` and a few others, which the user config can override or extend.

Common situations: Building listboxes/menus out of `<ol>`/`<ul>`; converting tables to grids with `role="grid"`; refactors where an element was swapped but the role kept; adopting jsx-a11y recommended config and finding many legacy role overrides.

Related errors


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