diff --git a/Cargo.lock b/Cargo.lock
index 202dd73262999..af8ff3dd5d258 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1599,7 +1599,7 @@ checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52"
[[package]]
name = "oxc"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"oxc_allocator",
"oxc_ast",
@@ -1683,7 +1683,7 @@ checksum = "42c447978900c2436dac665912cd298d3f9f6d4cac2551080328729ea52f840f"
[[package]]
name = "oxc_allocator"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"allocator-api2",
"hashbrown 0.16.1",
@@ -1697,7 +1697,7 @@ dependencies = [
[[package]]
name = "oxc_ast"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"bitflags",
"oxc_allocator",
@@ -1712,7 +1712,7 @@ dependencies = [
[[package]]
name = "oxc_ast_macros"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"phf",
"proc-macro2",
@@ -1757,7 +1757,7 @@ dependencies = [
[[package]]
name = "oxc_ast_visit"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"oxc_allocator",
"oxc_ast",
@@ -1792,7 +1792,7 @@ dependencies = [
[[package]]
name = "oxc_cfg"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"bitflags",
"itertools",
@@ -1804,7 +1804,7 @@ dependencies = [
[[package]]
name = "oxc_codegen"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"bitflags",
"cow-utils",
@@ -1826,7 +1826,7 @@ dependencies = [
[[package]]
name = "oxc_compat"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"cow-utils",
"oxc-browserslist",
@@ -1879,14 +1879,14 @@ dependencies = [
[[package]]
name = "oxc_data_structures"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"ropey",
]
[[package]]
name = "oxc_diagnostics"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"cow-utils",
"oxc-miette",
@@ -1895,7 +1895,7 @@ dependencies = [
[[package]]
name = "oxc_ecmascript"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"cow-utils",
"num-bigint",
@@ -1909,7 +1909,7 @@ dependencies = [
[[package]]
name = "oxc_estree"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"dragonbox_ecma",
"itoa",
@@ -1918,7 +1918,7 @@ dependencies = [
[[package]]
name = "oxc_formatter"
-version = "0.25.0"
+version = "0.26.0"
dependencies = [
"cow-utils",
"insta",
@@ -1951,7 +1951,7 @@ dependencies = [
[[package]]
name = "oxc_isolated_declarations"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"bitflags",
"insta",
@@ -1969,7 +1969,7 @@ dependencies = [
[[package]]
name = "oxc_language_server"
-version = "1.40.0"
+version = "1.41.0"
dependencies = [
"futures",
"papaya",
@@ -1983,7 +1983,7 @@ dependencies = [
[[package]]
name = "oxc_linter"
-version = "1.40.0"
+version = "1.41.0"
dependencies = [
"bitflags",
"constcat",
@@ -2056,7 +2056,7 @@ dependencies = [
[[package]]
name = "oxc_mangler"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"itertools",
"oxc_allocator",
@@ -2072,7 +2072,7 @@ dependencies = [
[[package]]
name = "oxc_minifier"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"cow-utils",
"insta",
@@ -2099,7 +2099,7 @@ dependencies = [
[[package]]
name = "oxc_minify_napi"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"mimalloc-safe",
"napi",
@@ -2138,7 +2138,7 @@ dependencies = [
[[package]]
name = "oxc_napi"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"napi",
"napi-build",
@@ -2152,7 +2152,7 @@ dependencies = [
[[package]]
name = "oxc_parser"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"bitflags",
"cow-utils",
@@ -2175,7 +2175,7 @@ dependencies = [
[[package]]
name = "oxc_parser_napi"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"mimalloc-safe",
"napi",
@@ -2226,7 +2226,7 @@ dependencies = [
[[package]]
name = "oxc_regular_expression"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"bitflags",
"insta",
@@ -2280,7 +2280,7 @@ dependencies = [
[[package]]
name = "oxc_semantic"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"insta",
"itertools",
@@ -2320,7 +2320,7 @@ dependencies = [
[[package]]
name = "oxc_span"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"compact_str",
"oxc-miette",
@@ -2333,7 +2333,7 @@ dependencies = [
[[package]]
name = "oxc_syntax"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"bitflags",
"cow-utils",
@@ -2406,7 +2406,7 @@ dependencies = [
[[package]]
name = "oxc_transform_napi"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"mimalloc-safe",
"napi",
@@ -2420,7 +2420,7 @@ dependencies = [
[[package]]
name = "oxc_transformer"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"base64",
"compact_str",
@@ -2451,7 +2451,7 @@ dependencies = [
[[package]]
name = "oxc_transformer_plugins"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"cow-utils",
"insta",
@@ -2478,7 +2478,7 @@ dependencies = [
[[package]]
name = "oxc_traverse"
-version = "0.109.0"
+version = "0.110.0"
dependencies = [
"itoa",
"oxc_allocator",
@@ -2494,7 +2494,7 @@ dependencies = [
[[package]]
name = "oxfmt"
-version = "0.25.0"
+version = "0.26.0"
dependencies = [
"bpaf",
"cow-utils",
@@ -2531,7 +2531,7 @@ dependencies = [
[[package]]
name = "oxlint"
-version = "1.40.0"
+version = "1.41.0"
dependencies = [
"bpaf",
"cow-utils",
diff --git a/crates/oxc_formatter/Cargo.toml b/crates/oxc_formatter/Cargo.toml
index 183f45e356e2b..90310227e3f48 100644
--- a/crates/oxc_formatter/Cargo.toml
+++ b/crates/oxc_formatter/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "oxc_formatter"
-version = "0.25.0"
+version = "0.26.0"
authors.workspace = true
categories.workspace = true
edition.workspace = true
diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs
index e888a64d1b9b5..893e7570baad3 100644
--- a/crates/oxc_linter/src/rules.rs
+++ b/crates/oxc_linter/src/rules.rs
@@ -555,6 +555,7 @@ pub(crate) mod jsx_a11y {
pub mod html_has_lang;
pub mod iframe_has_title;
pub mod img_redundant_alt;
+ pub mod interactive_supports_focus;
pub mod label_has_associated_control;
pub mod lang;
pub mod media_has_caption;
@@ -563,6 +564,7 @@ pub(crate) mod jsx_a11y {
pub mod no_aria_hidden_on_focusable;
pub mod no_autofocus;
pub mod no_distracting_elements;
+ pub mod no_interactive_element_to_noninteractive_role;
pub mod no_noninteractive_tabindex;
pub mod no_redundant_roles;
pub mod no_static_element_interactions;
@@ -993,10 +995,12 @@ oxc_macros::declare_all_lint_rules! {
jsx_a11y::html_has_lang,
jsx_a11y::iframe_has_title,
jsx_a11y::img_redundant_alt,
+ jsx_a11y::interactive_supports_focus,
jsx_a11y::label_has_associated_control,
jsx_a11y::lang,
jsx_a11y::media_has_caption,
jsx_a11y::mouse_events_have_key_events,
+ jsx_a11y::no_interactive_element_to_noninteractive_role,
jsx_a11y::no_noninteractive_tabindex,
jsx_a11y::no_static_element_interactions,
jsx_a11y::no_access_key,
diff --git a/crates/oxc_linter/src/rules/jsx_a11y/interactive_supports_focus.rs b/crates/oxc_linter/src/rules/jsx_a11y/interactive_supports_focus.rs
new file mode 100644
index 0000000000000..fb05648789e73
--- /dev/null
+++ b/crates/oxc_linter/src/rules/jsx_a11y/interactive_supports_focus.rs
@@ -0,0 +1,177 @@
+use oxc_ast::{
+ AstKind,
+ ast::{Expression, JSXAttributeItem, JSXAttributeValue, JSXExpression},
+};
+use oxc_diagnostics::OxcDiagnostic;
+use oxc_macros::declare_oxc_lint;
+use oxc_span::Span;
+
+use crate::{
+ AstNode,
+ context::LintContext,
+ globals::HTML_TAG,
+ rule::Rule,
+ utils::{
+ get_element_type, has_jsx_prop, is_hidden_from_screen_reader, is_interactive_element,
+ is_presentation_role,
+ },
+};
+
+fn interactive_supports_focus_diagnostic(span: Span) -> OxcDiagnostic {
+ OxcDiagnostic::warn("Elements with interactive roles must be focusable.")
+ .with_help("Interactive elements must be able to receive focus. In JSX, add a valid `tabIndex` prop.")
+ .with_label(span)
+}
+
+#[derive(Debug, Default, Clone)]
+pub struct InteractiveSupportsFocus;
+
+declare_oxc_lint!(
+ /// ### What it does
+ ///
+ /// Enforces that elements with interactive roles are focusable.
+ ///
+ /// ### Why is this bad?
+ ///
+ /// Interactive elements that are not focusable cannot be accessed by keyboard users,
+ /// making them inaccessible to users with disabilities who rely on keyboard navigation.
+ ///
+ /// ### Examples
+ ///
+ /// Examples of **incorrect** code for this rule:
+ /// ```jsx
+ ///
{}} />
+ ///
{}} />
+ /// ```
+ ///
+ /// Examples of **correct** code for this rule:
+ /// ```jsx
+ /// {}} tabIndex="0" />
+ ///