diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs
index 0f9bb3fc6aa48..501941442ac85 100644
--- a/crates/oxc_linter/src/rules.rs
+++ b/crates/oxc_linter/src/rules.rs
@@ -258,6 +258,7 @@ mod jsx_a11y {
pub mod no_aria_hidden_on_focusable;
pub mod no_autofocus;
pub mod no_distracting_elements;
+ pub mod no_redundant_roles;
pub mod prefer_tag_over_role;
pub mod role_has_required_aria_props;
pub mod role_support_aria_props;
@@ -518,6 +519,7 @@ oxc_macros::declare_all_lint_rules! {
jsx_a11y::no_access_key,
jsx_a11y::no_aria_hidden_on_focusable,
jsx_a11y::no_autofocus,
+ jsx_a11y::no_redundant_roles,
jsx_a11y::prefer_tag_over_role,
jsx_a11y::role_has_required_aria_props,
jsx_a11y::scope,
diff --git a/crates/oxc_linter/src/rules/jsx_a11y/no_redundant_roles.rs b/crates/oxc_linter/src/rules/jsx_a11y/no_redundant_roles.rs
new file mode 100644
index 0000000000000..90644c44e37a2
--- /dev/null
+++ b/crates/oxc_linter/src/rules/jsx_a11y/no_redundant_roles.rs
@@ -0,0 +1,122 @@
+use crate::{
+ context::LintContext,
+ rule::Rule,
+ utils::{get_element_type, has_jsx_prop_lowercase},
+ AstNode,
+};
+use oxc_ast::{
+ ast::{JSXAttributeItem, JSXAttributeValue},
+ AstKind,
+};
+use oxc_diagnostics::{
+ miette::{self, Diagnostic},
+ thiserror::{self, Error},
+};
+use oxc_macros::declare_oxc_lint;
+use oxc_span::Span;
+use phf::phf_map;
+
+#[derive(Debug, Error, Diagnostic)]
+#[error(
+ "eslint-plugin-jsx-a11y(no-redundant-roles): The element `{element}` has an implicit role of `{role}`. Defining this explicitly is redundant and should be avoided."
+)]
+#[diagnostic(
+ severity(warning),
+ help("Remove the redundant role `{role}` from the element `{element}`.")
+)]
+struct NoRedundantRolesDiagnostic {
+ #[label]
+ pub span: Span,
+ pub element: String,
+ pub role: String,
+}
+
+#[derive(Debug, Default, Clone)]
+pub struct NoRedundantRoles;
+
+declare_oxc_lint!(
+ /// ### What it does
+ /// Enforces that the explicit role property is not the same as implicit/default role property on element.
+ ///
+ /// ### Why is this bad?
+ /// Redundant roles can lead to confusion and verbosity in the codebase.
+ ///
+ /// ### Example
+ /// ```javascript
+ /// // Bad
+ ///
+ ///
+ /// // Good
+ ///
+ /// ```
+ NoRedundantRoles,
+ correctness
+);
+
+static DEFAULT_ROLE_EXCEPTIONS: phf::Map<&'static str, &'static str> = phf_map! {
+ "nav" =>"navigation",
+ "button" => "button",
+ "body" => "document",
+};
+
+impl Rule for NoRedundantRoles {
+ fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
+ if let AstKind::JSXOpeningElement(jsx_el) = node.kind() {
+ if let Some(component) = get_element_type(ctx, jsx_el) {
+ if let Some(JSXAttributeItem::Attribute(attr)) =
+ has_jsx_prop_lowercase(jsx_el, "role")
+ {
+ if let Some(JSXAttributeValue::StringLiteral(role_values)) = &attr.value {
+ let roles: Vec = role_values
+ .value
+ .split_whitespace()
+ .map(std::string::ToString::to_string)
+ .collect();
+ for role in &roles {
+ let exceptions = DEFAULT_ROLE_EXCEPTIONS.get(&component);
+ if exceptions.map_or(false, |set| set.contains(role)) {
+ ctx.diagnostic(NoRedundantRolesDiagnostic {
+ span: attr.span,
+ element: component.clone(),
+ role: role.to_string(),
+ });
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+#[test]
+fn test() {
+ use crate::rules::NoRedundantRoles;
+ use crate::tester::Tester;
+
+ fn settings() -> serde_json::Value {
+ serde_json::json!({
+ "jsx-a11y": {
+ "components": {
+ "Button": "button",
+ }
+ }
+ })
+ }
+
+ let pass = vec![
+ ("", None, None, None),
+ ("", None, None, None),
+ ("", None, None, None),
+ ("", None, None, None),
+ ("", None, Some(settings()), None),
+ ];
+
+ let fail = vec![
+ ("", None, None, None),
+ ("", None, None, None),
+ ("", None, Some(settings()), None),
+ ];
+
+ Tester::new(NoRedundantRoles::NAME, pass, fail).test_and_snapshot();
+}
diff --git a/crates/oxc_linter/src/snapshots/no_redundant_roles.snap b/crates/oxc_linter/src/snapshots/no_redundant_roles.snap
new file mode 100644
index 0000000000000..56f4b19f74967
--- /dev/null
+++ b/crates/oxc_linter/src/snapshots/no_redundant_roles.snap
@@ -0,0 +1,26 @@
+---
+source: crates/oxc_linter/src/tester.rs
+expression: no_redundant_roles
+---
+ ⚠ eslint-plugin-jsx-a11y(no-redundant-roles): The element `button` has an implicit role of `button`. Defining this explicitly is redundant and should be avoided.
+ ╭─[no_redundant_roles.tsx:1:1]
+ 1 │
+ · ─────────────
+ ╰────
+ help: Remove the redundant role `button` from the element `button`.
+
+ ⚠ eslint-plugin-jsx-a11y(no-redundant-roles): The element `body` has an implicit role of `document`. Defining this explicitly is redundant and should be avoided.
+ ╭─[no_redundant_roles.tsx:1:1]
+ 1 │