Skip to content

feat(linter/jsx-a11y): add no-noninteractive-element-interactions#22337

Merged
camchenry merged 7 commits into
oxc-project:mainfrom
pjtf93:feat/jsx-a11y-no-noninteractive-element-interactions
May 12, 2026
Merged

feat(linter/jsx-a11y): add no-noninteractive-element-interactions#22337
camchenry merged 7 commits into
oxc-project:mainfrom
pjtf93:feat/jsx-a11y-no-noninteractive-element-interactions

Conversation

@pjtf93

@pjtf93 pjtf93 commented May 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds the jsx-a11y/no-noninteractive-element-interactions rule to oxlint.

This rule reports mouse/keyboard event handlers on non-interactive elements or elements with non-interactive ARIA roles, while respecting interactive elements, interactive/abstract/presentation roles, hidden content, contentEditable, custom component mappings, and handler exception configuration.

Notes

  • Test coverage is based on the upstream eslint-plugin-jsx-a11y rule fixtures.
  • There is an earlier open PR for this same rule at feat(linter): implement jsx-a11y/no-noninteractive-element-interactions #17608; this PR is a fresh implementation/review pass for that rule.
  • This follows the structure and conventions from the related jsx-a11y/no-noninteractive-element-to-interactive-role work in feat(linter/jsx-a11y): implement no-noninteractive-element-to-interactive-role #21264.
  • The local test suite intentionally keeps separator and progressbar behavior tied to Oxc's existing shared ARIA role utilities for now.
  • Upstream expects role="separator" to report and role="progressbar" to pass. Oxc's current shared role classification leads to the opposite behavior for those two cases, so I would like maintainer guidance on whether this rule should add rule-specific overrides or whether the shared utilities should change.

Testing

  • cargo +stable fmt --check
  • cargo +stable test -p oxc_linter -- no_noninteractive_element_interactions
  • cargo +stable insta pending-snapshots
  • git diff --check

Disclosure

I used Pi and Codex with GPT-5.5 to help prepare these changes, and manually reviewed everything before opening this PR.

pjtf93 added 4 commits May 11, 2026 21:57
Add the jsx-a11y/no-noninteractive-element-interactions rule, generated rule registration, and snapshot coverage.

Verification: cargo +stable test -p oxc_linter -- no_noninteractive_element_interactions
Add the remaining upstream fixtures for no-noninteractive-element-interactions while leaving separator/progressbar behavior for PR discussion.
Remove local-only redundant fixtures while keeping upstream parity and targeted regression coverage.
@pjtf93 pjtf93 marked this pull request as ready for review May 12, 2026 00:36
@pjtf93 pjtf93 requested a review from camc314 as a code owner May 12, 2026 00:36
@camchenry

Copy link
Copy Markdown
Member

Upstream expects role="separator" to report and role="progressbar" to pass. Oxc's current shared role classification leads to the opposite behavior for those two cases, so I would like maintainer guidance on whether this rule should add rule-specific overrides or whether the shared utilities should change.

We should determine if our implementation is correct, if eslint-plugin-jsx-a11y is correct, or we need to change something based on the ARIA specification. I think it's okay to diverge if it's more accurate for accessibility.

@codspeed-hq

codspeed-hq Bot commented May 12, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 4 untouched benchmarks
⏩ 47 skipped benchmarks1


Comparing pjtf93:feat/jsx-a11y-no-noninteractive-element-interactions (a814da0) with main (41fcdcf)

Open in CodSpeed

Footnotes

  1. 47 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@camchenry

Copy link
Copy Markdown
Member

@pjtf93 thanks for the contribution, there are some lint errors that need to be fixed.

@mehm8128

mehm8128 commented May 12, 2026

Copy link
Copy Markdown
Contributor

FYI
progressbar
The original plugin has an inconsistentency in the utils between isInteractiveElement.js and isInteractiveRole. While the former classifies progressbar as a nonInteractiveRole, the latter classifies it as an interactiveRole.

I think the comment

The progressbar is descended from widget, but in practice, its
value is always readonly, so we treat it as a non-interactive role.

is correct, so we should classify it as a non-interactive role.

separator
We classify roles as interactive or non-interactive role based on whether they inherit the widget role. According to the spec, separator inherits the structure role when it is not focusable, and the widget role when it is focusable. Therefore, if we can easily determine whether it is focusable in a given situation, we can report it more accurately.

@camchenry camchenry self-assigned this May 12, 2026
@camc314 camc314 added the A-linter Area - Linter label May 12, 2026
@pjtf93

pjtf93 commented May 12, 2026

Copy link
Copy Markdown
Contributor Author

@camchenry Thank you for taking a look, I fixed the lint errors and aligned the PR with what @mehm8128 mentioned

@mehm8128 Thanks for digging into this.

I updated the rule along those lines:

  • Kept progressbar non-interactive, matching the existing isInteractiveElement special-case / readonly behavior.
  • Added rule-local separator handling based on focusability.

For separator, the rule now treats non-focusable separators as static structure and reports them, while focusable separators are treated as interactive widgets and pass validation.

I kept this logic local to the rule instead of changing the shared role utilities, since separator behavior depends on element context (tabIndex / native focusability), not just the role string.

Also added tests covering both non-focusable and focusable separators.

@camchenry camchenry left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is mergeable as-is. We can always iterate on the semantics later, but this seems aligned with the original rule.

@camchenry camchenry added the 0-merge Merge with Graphite Merge Queue label May 12, 2026
@graphite-app

graphite-app Bot commented May 12, 2026

Copy link
Copy Markdown
Contributor

Merge activity

  • May 12, 6:13 PM UTC: @pjtf93 we removed the merge queue label because we could not find a Graphite account associated with your GitHub profile.

You must have a Graphite account in order to use the merge queue. Create an account and try again using this link

@graphite-app graphite-app Bot removed the 0-merge Merge with Graphite Merge Queue label May 12, 2026
@camchenry

Copy link
Copy Markdown
Member

I always forget I can't use 0-merge 🤦

@camchenry camchenry merged commit 2749422 into oxc-project:main May 12, 2026
28 checks passed
overlookmotel added a commit that referenced this pull request May 15, 2026
# Oxlint
### 🚀 Features

- 5478fb5 linter/jsdoc: Implement `require-throws-description` rule
(#22386) (Mikhail Baev)
- b46d4de linter: Add `--debug` options and add per-rule timing info
(#22282) (camchenry)
- c73225e linter/eslint: Implement `prefer-arrow-callback` rule (#22312)
(박천(Cheon Park))
- de82b59 linter: Add support for `eslint-plugin-jsx-a11y-x` (#22356)
(mehm8128)
- b170da3 linter: Implement no-implicit-globals (#22249) (Jovi De
Croock)
- f44b6c8 linter: Fill schemas `DummyRuleMap` with built-in rules
(#22288) (Sysix)
- 5cdb80d linter/jsx-a11y/: Implement
no-interactive-element-to-noninteractive-role (#22332) (anarefolio)
- 2749422 linter/jsx-a11y: Add no-noninteractive-element-interactions
(#22337) (Pablo Tovar)
- ba2a1d3 linter/jsdoc: Implement `require-throws-type` rule (#22358)
(Mikhail Baev)
- d90729d linter/jsx-a11y: Implement control-has-associated-label
(#21985) (mehm8128)
- 1d04903 linter/jsdoc: Implement `require-yields-type` rule (#22331)
(Mikhail Baev)

### 🐛 Bug Fixes

- 04c4609 linter/no-nullable-type-assertion-style: Mark as suggestion
(#22450) (camc314)
- 1c2b7ec linter/no-unused-vars: Handle shadowed self assignments
(#22387) (camc314)
- 9faa1d5 linter/no-noninteractive-tabindex: Check conditional
expressions (#22435) (camc314)
- 0854b3a linter/prefer-arrow-callback: Preserve TSX generic arrows in
fixer (#22434) (camc314)
- 410b814 linter: Supply `source_type` to codegen fixer (#22433)
(camc314)
- 3c1bb6f linter: Skip per-node dispatch for run_once-only rules in
large files (#22398) (Connor Shea)
- 5206cde linter/no-unused-vars: Improve type-only rest parameters
diagnostic (#22385) (camc314)
- c9a22b5 linter/consistent-function-scoping: Allow imported bindings
(#22384) (camc314)
- c1e966d linter: Report type-only unused parameters in no-unused-vars
(#22368) (camchenry)
- 4818d98 linter: Check whether path is under root before ignoring it
(#20101) (Leonabcd123)
- 41fcdcf linter: Fix rule count not including override rules (#19898)
(Daniel Osmond)
- 59b4f0e linter: Fix 'explicit-module-boundary-types' false positive
with 'allowOverloadFunctions' (#22341) (camchenry)

### ⚡ Performance

- 6d42395 linter: Narrow no-unsafe-optional-chaining dispatch (#22437)
(camchenry)
- 08595fb linter: Optimize no-unreachable (#22397) (camchenry)
- 3b46a8d linter: Optimize `no-loss-of-precision` (#22395) (camchenry)
- b3e2dc9 linter: Optimize `oxc/bad-array-method-on-arguments` (#22393)
(camchenry)

### 📚 Documentation

- dcbf62c linter: Remove some duplicate spaces (#22359) (camc314)
# Oxfmt
### 💥 BREAKING CHANGES

- 21bb5d1 oxfmt: [**BREAKING**] Avoid config pre-scan (#22258)
(leaysgur)

### 🐛 Bug Fixes

- 441d724 oxfmt: Fix "race probe" logic with unit tests (#22378)
(leaysgur)
- e49ee26 formatter: Respect `singleQuote` for jsdoc `import()` type
paths (#22353) (Colin Lienard)
- 43b9978 formatter/sort_imports: Treat subpath imports as internal
(#22440) (leaysgur)
- 7c5cfa0 formatter: Handle jsx trailing comment with parens (#22370)
(leaysgur)
- ac5f120 formatter: Fix erroneous formatting inside a template literal
with parentheses (#22262) (Jovi De Croock)
- 3c53a95 formatter/sort_imports: Handle ignore comment as boundary
(#22369) (leaysgur)
- 4dd83dd oxfmt: Send expandedStates variants as shared refs (#22366)
(leaysgur)
- 055cc61 formatter: Expand JSX logical chain with leading line comment
(#22346) (leaysgur)
- 8046222 formatter: Preserve type alias comment break (#22261) (Jovi De
Croock)

### ⚡ Performance

- 123c493 oxfmt: Reduce more syscalls (#22380) (leaysgur)

Co-authored-by: overlookmotel <557937+overlookmotel@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-linter Area - Linter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants