diff --git a/.changeset/html-no-redundant-roles.md b/.changeset/html-no-redundant-roles.md
new file mode 100644
index 000000000000..6a6152394d00
--- /dev/null
+++ b/.changeset/html-no-redundant-roles.md
@@ -0,0 +1,10 @@
+---
+"@biomejs/biome": minor
+---
+
+Added the HTML lint rule [`noRedundantRoles`](https://biomejs.dev/linter/rules/no-redundant-roles/). This rule enforces that explicit `role` attributes are not the same as the implicit/default role of an HTML element. It supports HTML, Vue, Svelte, and Astro files.
+
+```html
+
+
+```
diff --git a/Cargo.lock b/Cargo.lock
index afa837df5006..8f17c654bec3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -809,6 +809,7 @@ version = "0.5.7"
dependencies = [
"biome_analyze",
"biome_analyze_macros",
+ "biome_aria",
"biome_aria_metadata",
"biome_console",
"biome_deserialize",
@@ -902,6 +903,7 @@ dependencies = [
name = "biome_html_syntax"
version = "0.5.7"
dependencies = [
+ "biome_aria",
"biome_html_factory",
"biome_html_parser",
"biome_rowan",
diff --git a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs
index d050614940e6..70047e79c93d 100644
--- a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs
+++ b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs
@@ -249,6 +249,14 @@ pub(crate) fn migrate_eslint_any_rule(
.get_or_insert(Default::default());
rule.set_level(rule.level().max(rule_severity.into()));
}
+ "@html-eslint/no-redundant-role" => {
+ let group = rules.a11y.get_or_insert_with(Default::default);
+ let rule = group
+ .unwrap_group_as_mut()
+ .no_redundant_roles
+ .get_or_insert(Default::default());
+ rule.set_level(rule.level().max(rule_severity.into()));
+ }
"@html-eslint/require-button-type" => {
let group = rules.a11y.get_or_insert_with(Default::default);
let rule = group
diff --git a/crates/biome_html_analyze/Cargo.toml b/crates/biome_html_analyze/Cargo.toml
index ff6ff38f2886..3f14c17ed70e 100644
--- a/crates/biome_html_analyze/Cargo.toml
+++ b/crates/biome_html_analyze/Cargo.toml
@@ -17,6 +17,7 @@ name = "html_analyzer"
[dependencies]
biome_analyze = { workspace = true }
biome_analyze_macros = { workspace = true }
+biome_aria = { workspace = true }
biome_aria_metadata = { workspace = true }
biome_console = { workspace = true }
biome_deserialize = { workspace = true }
diff --git a/crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs b/crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs
new file mode 100644
index 000000000000..d56049c6598f
--- /dev/null
+++ b/crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs
@@ -0,0 +1,130 @@
+use biome_analyze::{
+ Ast, FixKind, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule,
+};
+use biome_aria::AriaRoles;
+use biome_aria_metadata::AriaRole;
+use biome_console::markup;
+use biome_diagnostics::Severity;
+use biome_html_syntax::{AnyHtmlElement, HtmlAttribute, HtmlFileSource};
+use biome_rowan::{AstNode, BatchMutationExt, Text};
+use biome_rule_options::no_redundant_roles::NoRedundantRolesOptions;
+
+use crate::HtmlRuleAction;
+
+declare_lint_rule! {
+ /// Enforce explicit `role` property is not the same as implicit/default role property on an element.
+ ///
+ /// :::note
+ /// In `.html` files, all elements are treated as native HTML elements.
+ ///
+ /// In component-based frameworks (Vue, Svelte, Astro), only native HTML element names are checked.
+ /// PascalCase names like `