Repository: biomejs/biome
Pull request #6082: feat(lint): implement useUniqueElementIds
Author: login: mehm8128
Labels: name: A-Project
description: Area: project
name: A-Linter
description: Area: linter
name: L-JavaScript
description: Language: JavaScript and super languages
name: A-Diagnostic
description: Area: diagnostocis
Created: 2025-05-19T13:46:44Z | Merged: 2025-05-22T14:11:04Z

<!--
	Thanks for submitting a Pull Request! We appreciate you spending the time to work on these changes.
	Please provide enough information so that others can review your PR.
	Once created, your PR will be automatically labeled according to changed files.
	Learn more about contributing: https://github.com/biomejs/biome/blob/main/CONTRIBUTING.md
-->

## Summary

close https://github.com/biomejs/biome/issues/6036
<!-- Explain the **motivation** for making this change. What existing problem does the pull request solve?-->

<!-- Link any relevant issues if necessary or include a transcript of any Discord discussion. -->

## Test Plan

<!-- What demonstrates that your implementation is correct? -->
See `invalid.js` and `valid.js` test cases. But in my environment, insta test didn't work correctly and panicked when I run `just test-lintrule noStaticElementId`, so only one test case of `invalid.js` reported an error even though quick_test reported errors against all test cases. I'd like you to help.

And how can I resolve conflict? is there the way to re-generate conflicting files based on other files?


Diff:
diff --git a/.changeset/famous-crabs-bathe.md b/.changeset/famous-crabs-bathe.md
new file mode 100644
index 000000000000..48efbaf62c02
--- /dev/null
+++ b/.changeset/famous-crabs-bathe.md
@@ -0,0 +1,24 @@
+---
+"@biomejs/biome": minor
+---
+
+Added the rule [useUniqueElementIds](https://biomejs.dev/linter/rules/use-unique-element-ids/).
+This rule disallows the use of static IDs in React components. It encourages to generate unique IDs for accessibility purposes using [`useId`](https://react.dev/reference/react/useId).
+
+The following code is now reported as invalid:
+
+```jsx
+function App() {
+  return <div id="static-id" />;
+}
+```
+
+The following code is now reported as valid:
+
+```jsx
+import { useId } from "react";
+function App() {
+  const id = useId();
+  return <div id={id} />;
+}
+```
diff --git a/crates/biome_configuration/src/analyzer/linter/rules.rs b/crates/biome_configuration/src/analyzer/linter/rules.rs
index 4272a63684a5..3f18eb774326 100644
--- a/crates/biome_configuration/src/analyzer/linter/rules.rs
+++ b/crates/biome_configuration/src/analyzer/linter/rules.rs
@@ -397,6 +397,7 @@ pub enum RuleName {
     UseThrowOnlyError,
     UseTopLevelRegex,
     UseTrimStartEnd,
+    UseUniqueElementIds,
     UseValidAnchor,
     UseValidAriaProps,
     UseValidAriaRole,
@@ -723,6 +724,7 @@ impl RuleName {
             Self::UseThrowOnlyError => "useThrowOnlyError",
             Self::UseTopLevelRegex => "useTopLevelRegex",
             Self::UseTrimStartEnd => "useTrimStartEnd",
+            Self::UseUniqueElementIds => "useUniqueElementIds",
             Self::UseValidAnchor => "useValidAnchor",
             Self::UseValidAriaProps => "useValidAriaProps",
             Self::UseValidAriaRole => "useValidAriaRole",
@@ -1019,7 +1021,7 @@ impl RuleName {
             Self::UseMediaCaption => RuleGroup::A11y,
             Self::UseNamedOperation => RuleGroup::Nursery,
             Self::UseNamespaceKeyword => RuleGroup::Suspicious,
-            Self::UseNamingConvention => RuleGroup::Nursery,
+            Self::UseNamingConvention => RuleGroup::Style,
             Self::UseNodeAssertStrict => RuleGroup::Style,
             Self::UseNodejsImportProtocol => RuleGroup::Style,
             Self::UseNumberNamespace => RuleGroup::Style,
@@ -1045,6 +1047,7 @@ impl RuleName {
             Self::UseThrowOnlyError => RuleGroup::Style,
             Self::UseTopLevelRegex => RuleGroup::Performance,
             Self::UseTrimStartEnd => RuleGroup::Style,
+            Self::UseUniqueElementIds => RuleGroup::Nursery,
             Self::UseValidAnchor => RuleGroup::A11y,
             Self::UseValidAriaProps => RuleGroup::A11y,
             Self::UseValidAriaRole => RuleGroup::A11y,
@@ -1376,6 +1379,7 @@ impl std::str::FromStr for RuleName {
             "useThrowOnlyError" => Ok(Self::UseThrowOnlyError),
             "useTopLevelRegex" => Ok(Self::UseTopLevelRegex),
             "useTrimStartEnd" => Ok(Self::UseTrimStartEnd),
+            "useUniqueElementIds" => Ok(Self::UseUniqueElementIds),
             "useValidAnchor" => Ok(Self::UseValidAnchor),
             "useValidAriaProps" => Ok(Self::UseValidAriaProps),
             "useValidAriaRole" => Ok(Self::UseValidAriaRole),
@@ -4822,6 +4826,10 @@ pub struct Nursery {
     #[serde(skip_serializing_if = "Option::is_none")]
     pub use_symbol_description:
         Option<RuleConfiguration<biome_js_analyze::options::UseSymbolDescription>>,
+    #[doc = "Prevent the usage of static string literal id attribute on elements."]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub use_unique_element_ids:
+        Option<RuleConfiguration<biome_js_analyze::options::UseUniqueElementIds>>,
 }
 impl Nursery {
     const GROUP_NAME: &'static str = "nursery";
@@ -4862,6 +4870,7 @@ impl Nursery {
         "useSingleJsDocAsterisk",
         "useSortedClasses",
         "useSymbolDescription",
+        "useUniqueElementIds",
     ];
     const RECOMMENDED_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7]),
@@ -4910,6 +4919,7 @@ impl Nursery {
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35]),
+        RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36]),
     ];
 }
 impl RuleGroupExt for Nursery {
@@ -5101,6 +5111,11 @@ impl RuleGroupExt for Nursery {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35]));
             }
         }
+        if let Some(rule) = self.use_unique_element_ids.as_ref() {
+            if rule.is_enabled() {
+                index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36]));
+            }
+        }
         index_set
     }
     fn get_disabled_rules(&self) -> FxHashSet<RuleFilter<'static>> {
@@ -5285,6 +5300,11 @@ impl RuleGroupExt for Nursery {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35]));
             }
         }
+        if let Some(rule) = self.use_unique_element_ids.as_ref() {
+            if rule.is_disabled() {
+                index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36]));
+            }
+        }
         index_set
     }
     #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"]
@@ -5459,6 +5479,10 @@ impl RuleGroupExt for Nursery {
                 .use_symbol_description
                 .as_ref()
                 .map(|conf| (conf.level(), conf.get_options())),
+            "useUniqueElementIds" => self
+                .use_unique_element_ids
+                .as_ref()
+                .map(|conf| (conf.level(), conf.get_options())),
             _ => None,
         }
     }
@@ -5503,6 +5527,7 @@ impl From<GroupPlainConfiguration> for Nursery {
             use_single_js_doc_asterisk: Some(value.into()),
             use_sorted_classes: Some(value.into()),
             use_symbol_description: Some(value.into()),
+            use_unique_element_ids: Some(value.into()),
         }
     }
 }
diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs
index 04fbdb3d643c..7f6b942e6e6f 100644
--- a/crates/biome_diagnostics_categories/src/categories.rs
+++ b/crates/biome_diagnostics_categories/src/categories.rs
@@ -206,6 +206,7 @@ define_categories! {
     "lint/nursery/useSortedClasses": "https://biomejs.dev/linter/rules/use-sorted-classes",
     "lint/nursery/useSortedProperties": "https://biomejs.dev/linter/rules/use-sorted-properties",
     "lint/nursery/useSymbolDescription": "https://biomejs.dev/linter/rules/use-symbol-description",
+    "lint/nursery/useUniqueElementIds": "https://biomejs.dev/linter/rules/use-unique-element-ids",
     "lint/performance/noAccumulatingSpread": "https://biomejs.dev/linter/rules/no-accumulating-spread",
     "lint/performance/noBarrelFile": "https://biomejs.dev/linter/rules/no-barrel-file",
     "lint/performance/noDelete": "https://biomejs.dev/linter/rules/no-delete",
diff --git a/crates/biome_js_analyze/src/lint/nursery.rs b/crates/biome_js_analyze/src/lint/nursery.rs
index 8200a5c20bcb..a745b17b8ed6 100644
--- a/crates/biome_js_analyze/src/lint/nursery.rs
+++ b/crates/biome_js_analyze/src/lint/nursery.rs
@@ -35,4 +35,5 @@ pub mod use_parse_int_radix;
 pub mod use_single_js_doc_asterisk;
 pub mod use_sorted_classes;
 pub mod use_symbol_description;
-declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_await_in_loop :: NoAwaitInLoop , self :: no_bitwise_operators :: NoBitwiseOperators , self :: no_constant_binary_expression :: NoConstantBinaryExpression , self :: no_destructured_props :: NoDestructuredProps , self :: no_floating_promises :: NoFloatingPromises , self :: no_global_dirname_filename :: NoGlobalDirnameFilename , self :: no_import_cycles :: NoImportCycles , self :: no_nested_component_definitions :: NoNestedComponentDefinitions , self :: no_noninteractive_element_interactions :: NoNoninteractiveElementInteractions , self :: no_process_global :: NoProcessGlobal , self :: no_restricted_elements :: NoRestrictedElements , self :: no_secrets :: NoSecrets , self :: no_shadow :: NoShadow , self :: no_ts_ignore :: NoTsIgnore , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unwanted_polyfillio :: NoUnwantedPolyfillio , self :: no_useless_backref_in_regex :: NoUselessBackrefInRegex , self :: no_useless_escape_in_string :: NoUselessEscapeInString , self :: no_useless_undefined :: NoUselessUndefined , self :: use_adjacent_getter_setter :: UseAdjacentGetterSetter , self :: use_consistent_object_definition :: UseConsistentObjectDefinition , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_exports_last :: UseExportsLast , self :: use_for_component :: UseForComponent , self :: use_google_font_preconnect :: UseGoogleFontPreconnect , self :: use_iterable_callback_return :: UseIterableCallbackReturn , self :: use_numeric_separators :: UseNumericSeparators , self :: use_parse_int_radix :: UseParseIntRadix , self :: use_single_js_doc_asterisk :: UseSingleJsDocAsterisk , self :: use_sorted_classes :: UseSortedClasses , self :: use_symbol_description :: UseSymbolDescription ,] } }
+pub mod use_unique_element_ids;
+declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_await_in_loop :: NoAwaitInLoop , self :: no_bitwise_operators :: NoBitwiseOperators , self :: no_constant_binary_expression :: NoConstantBinaryExpression , self :: no_destructured_props :: NoDestructuredProps , self :: no_floating_promises :: NoFloatingPromises , self :: no_global_dirname_filename :: NoGlobalDirnameFilename , self :: no_import_cycles :: NoImportCycles , self :: no_nested_component_definitions :: NoNestedComponentDefinitions , self :: no_noninteractive_element_interactions :: NoNoninteractiveElementInteractions , self :: no_process_global :: NoProcessGlobal , self :: no_restricted_elements :: NoRestrictedElements , self :: no_secrets :: NoSecrets , self :: no_shadow :: NoShadow , self :: no_ts_ignore :: NoTsIgnore , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unwanted_polyfillio :: NoUnwantedPolyfillio , self :: no_useless_backref_in_regex :: NoUselessBackrefInRegex , self :: no_useless_escape_in_string :: NoUselessEscapeInString , self :: no_useless_undefined :: NoUselessUndefined , self :: use_adjacent_getter_setter :: UseAdjacentGetterSetter , self :: use_consistent_object_definition :: UseConsistentObjectDefinition , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_exports_last :: UseExportsLast , self :: use_for_component :: UseForComponent , self :: use_google_font_preconnect :: UseGoogleFontPreconnect , self :: use_iterable_callback_return :: UseIterableCallbackReturn , self :: use_numeric_separators :: UseNumericSeparators , self :: use_parse_int_radix :: UseParseIntRadix , self :: use_single_js_doc_asterisk :: UseSingleJsDocAsterisk , self :: use_sorted_classes :: UseSortedClasses , self :: use_symbol_description :: UseSymbolDescription , self :: use_unique_element_ids :: UseUniqueElementIds ,] } }
diff --git a/crates/biome_js_analyze/src/lint/nursery/use_unique_element_ids.rs b/crates/biome_js_analyze/src/lint/nursery/use_unique_element_ids.rs
new file mode 100644
index 000000000000..0c184526b547
--- /dev/null
+++ b/crates/biome_js_analyze/src/lint/nursery/use_unique_element_ids.rs
@@ -0,0 +1,123 @@
+use biome_analyze::{Rule, RuleDiagnostic, RuleDomain, context::RuleContext, declare_lint_rule};
+use biome_console::markup;
+use biome_diagnostics::Severity;
+use biome_js_semantic::SemanticModel;
+use biome_js_syntax::{
+    AnyJsExpression, AnyJsxAttributeValue, JsCallExpression, JsPropertyObjectMember, JsxAttribute,
+    jsx_ext::AnyJsxElement,
+};
+use biome_rowan::{AstNode, declare_node_union};
+
+use crate::react::{ReactApiCall, ReactCreateElementCall};
+use crate::services::semantic::Semantic;
+
+declare_lint_rule! {
+    /// Prevent the usage of static string literal `id` attribute on elements.
+    ///
+    /// In React, hardcoding IDs is discouraged because IDs have to be unique in the DOM.
+    /// You should use [`useId`](https://react.dev/reference/react/useId) to generate unique IDs for accessibility purposes.
+    ///
+    /// Please keep in mind this rule doesn't check whether ids are actually unique or not, and does check whether static literal id isn't passed to the elements or not. So you're encouraged to check by yourself if the ids are actually unique.
+    ///
+    /// ## Examples
+    ///
+    /// ### Invalid
+    ///
+    /// ```jsx,expect_diagnostic
+    /// <div id="foo">bar</div>;
+    /// ```
+    ///
+    /// ```jsx,expect_diagnostic
+    /// React.createElement("div", { id: "foo" });
+    /// ```
+    ///
+    /// ### Valid
+    ///
+    /// ```jsx
+    /// const id = useId();
+    /// <div id={id}>bar</div>;
+    /// ```
+    ///
+    /// ```jsx
+    /// const id = useId();
+    /// React.createElement("div", { id });
+    /// ```
+    ///
+    pub UseUniqueElementIds {
+        version: "2.0.0",
+        name: "useUniqueElementIds",
+        language: "jsx",
+        recommended: false,
+        severity: Severity::Error,
+        domains: &[RuleDomain::React],
+    }
+}
+
+declare_node_union! {
+    pub IdProp = JsxAttribute | JsPropertyObjectMember
+}
+
+declare_node_union! {
+    pub UseUniqueElementIdsQuery = AnyJsxElement | JsCallExpression
+}
+
+impl UseUniqueElementIdsQuery {
+    fn find_id_attribute(&self, model: &SemanticModel) -> Option<IdProp> {
+        match self {
+            Self::AnyJsxElement(jsx) => jsx.find_attribute_by_name("id").map(IdProp::from),
+            Self::JsCallExpression(expression) => {
+                let react_create_element =
+                    ReactCreateElementCall::from_call_expression(expression, model)?;
+                react_create_element
+                    .find_prop_by_name("id")
+                    .map(IdProp::from)
+            }
+        }
+    }
+}
+
+impl Rule for UseUniqueElementIds {
+    type Query = Semantic<UseUniqueElementIdsQuery>;
+    type State = ();
+    type Signals = Option<Self::State>;
+    type Options = ();
+
+    fn run(ctx: &RuleContext<Self>) -> Self::Signals {
+        let node = ctx.query();
+        let model = ctx.model();
+        let id_attribute = node.find_id_attribute(model)?;
+
+        match id_attribute {
+            IdProp::JsxAttribute(jsx_attribute) => {
+                let jsx_any_attribute_value = jsx_attribute.initializer()?.value().ok()?;
+                if matches!(jsx_any_attribute_value, AnyJsxAttributeValue::JsxString(_)) {
+                    return Some(());
+                }
+                None
+            }
+            IdProp::JsPropertyObjectMember(js_object_member) => {
+                let expression = js_object_member.value().ok()?;
+                if matches!(expression, AnyJsExpression::AnyJsLiteralExpression(_)) {
+                    return Some(());
+                }
+                None
+            }
+        }
+    }
+
+    fn diagnostic(ctx: &RuleContext<Self>, _state: &Self::State) -> Option<RuleDiagnostic> {
+        let node = ctx.query();
+        Some(
+            RuleDiagnostic::new(
+                rule_category!(),
+                node.range(),
+                markup! {
+                    <Emphasis>"id"</Emphasis>" attribute should not be a static string literal. Generate unique IDs using "<Emphasis>"useId()"</Emphasis>"."
+                },
+            )
+            .note(markup! {
+                "In React, if you hardcode IDs and use the component multiple times, it can lead to duplicate IDs in the DOM. Instead, generate unique IDs using "<Emphasis>"useId()"</Emphasis>"."
+            }),
+        )
+    }
+}
diff --git a/crates/biome_js_analyze/src/options.rs b/crates/biome_js_analyze/src/options.rs
index 2888b0aaa645..1ad0379f8cb1 100644
--- a/crates/biome_js_analyze/src/options.rs
+++ b/crates/biome_js_analyze/src/options.rs
@@ -443,6 +443,8 @@ pub type UseTopLevelRegex =
     <lint::performance::use_top_level_regex::UseTopLevelRegex as biome_analyze::Rule>::Options;
 pub type UseTrimStartEnd =
     <lint::style::use_trim_start_end::UseTrimStartEnd as biome_analyze::Rule>::Options;
+pub type UseUniqueElementIds =
+    <lint::nursery::use_unique_element_ids::UseUniqueElementIds as biome_analyze::Rule>::Options;
 pub type UseValidAnchor =
     <lint::a11y::use_valid_anchor::UseValidAnchor as biome_analyze::Rule>::Options;
 pub type UseValidAriaProps =
diff --git a/crates/biome_js_analyze/tests/specs/nursery/useUniqueElementIds/invalid.jsx b/crates/biome_js_analyze/tests/specs/nursery/useUniqueElementIds/invalid.jsx
new file mode 100644
index 000000000000..4d45c1cc127b
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/useUniqueElementIds/invalid.jsx
@@ -0,0 +1,28 @@
+function Foo() {
+	return <div id="foo"></div>;
+}
+
+function Foo() {
+	return (
+		<div id="foo">
+			<div>bar</div>
+		</div>
+	);
+}
+
+function Foo() {
+	return (
+		<div>
+			<div id="foo">bar</div>
+		</div>
+	);
+}
+
+function Foo() {
+	return React.createElement("div", { id: "foo" });
+}
+
+import { createElement } from "react";
+function Foo() {
+	return createElement("div", { id: "foo" });
+}
diff --git a/crates/biome_js_analyze/tests/specs/nursery/useUniqueElementIds/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/useUniqueElementIds/invalid.jsx.snap
new file mode 100644
index 000000000000..1ba01c349b8c
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/useUniqueElementIds/invalid.jsx.snap
@@ -0,0 +1,120 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: invalid.jsx
+---
+# Input
+```jsx
+function Foo() {
+	return <div id="foo"></div>;
+}
+
+function Foo() {
+	return (
+		<div id="foo">
+			<div>bar</div>
+		</div>
+	);
+}
+
+function Foo() {
+	return (
+		<div>
+			<div id="foo">bar</div>
+		</div>
+	);
+}
+
+function Foo() {
+	return React.createElement("div", { id: "foo" });
+}
+
+import { createElement } from "react";
+function Foo() {
+	return createElement("div", { id: "foo" });
+}
+
+```
+
+# Diagnostics
+```
+invalid.jsx:2:9 lint/nursery/useUniqueElementIds ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+  × id attribute should not be a static string literal. Generate unique IDs using useId().
+  
+    1 │ function Foo() {
+  > 2 │ 	return <div id="foo"></div>;
+      │ 	       ^^^^^^^^^^^^^^
+    3 │ }
+    4 │ 
+  
+  i In React, if you hardcode IDs and use the component multiple times, it can lead to duplicate IDs in the DOM. Instead, generate unique IDs using useId().
+  
+
+```
+
+```
+invalid.jsx:7:3 lint/nursery/useUniqueElementIds ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+  × id attribute should not be a static string literal. Generate unique IDs using useId().
+  
+    5 │ function Foo() {
+    6 │ 	return (
+  > 7 │ 		<div id="foo">
+      │ 		^^^^^^^^^^^^^^
+    8 │ 			<div>bar</div>
+    9 │ 		</div>
+  
+  i In React, if you hardcode IDs and use the component multiple times, it can lead to duplicate IDs in the DOM. Instead, generate unique IDs using useId().
+  
+
+```
+
+```
+invalid.jsx:16:4 lint/nursery/useUniqueElementIds ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+  × id attribute should not be a static string literal. Generate unique IDs using useId().
+  
+    14 │ 	return (
+    15 │ 		<div>
+  > 16 │ 			<div id="foo">bar</div>
+       │ 			^^^^^^^^^^^^^^
+    17 │ 		</div>
+    18 │ 	);
+  
+  i In React, if you hardcode IDs and use the component multiple times, it can lead to duplicate IDs in the DOM. Instead, generate unique IDs using useId().
+  
+
+```
+
+```
+invalid.jsx:22:9 lint/nursery/useUniqueElementIds ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+  × id attribute should not be a static string literal. Generate unique IDs using useId().
+  
+    21 │ function Foo() {
+  > 22 │ 	return React.createElement("div", { id: "foo" });
+       │ 	       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+    23 │ }
+    24 │ 
+  
+  i In React, if you hardcode IDs and use the component multiple times, it can lead to duplicate IDs in the DOM. Instead, generate unique IDs using useId().
+  
+
+```
+
+```
+invalid.jsx:27:9 lint/nursery/useUniqueElementIds ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+  × id attribute should not be a static string literal. Generate unique IDs using useId().
+  
+    25 │ import { createElement } from "react";
+    26 │ function Foo() {
+  > 27 │ 	return createElement("div", { id: "foo" });
+       │ 	       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+    28 │ }
+    29 │ 
+  
+  i In React, if you hardcode IDs and use the component multiple times, it can lead to duplicate IDs in the DOM. Instead, generate unique IDs using useId().
+  
+
+```
diff --git a/crates/biome_js_analyze/tests/specs/nursery/useUniqueElementIds/valid.jsx b/crates/biome_js_analyze/tests/specs/nursery/useUniqueElementIds/valid.jsx
new file mode 100644
index 000000000000..182b3575426a
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/useUniqueElementIds/valid.jsx
@@ -0,0 +1,46 @@
+// should not generate diagnostics;
+function Foo() {
+	const id = useId();
+	return <div id={id}></div>;
+}
+
+function Foo() {
+	const id = useId();
+	return (
+		<div id={id}>
+			<div>bar</div>
+		</div>
+	);
+}
+
+function Foo() {
+	const id = useId();
+	return (
+		<div>
+			<div id={id}>bar</div>
+		</div>
+	);
+}
+
+function Foo() {
+	const id = crypto.randomUUID();
+	return <div id={id}></div>;
+}
+
+function Foo({ id }) {
+	return <div id={id}></div>;
+}
+
+function Foo() {
+	const id = useId();
+	return React.createElement("div", { id });
+}
+
+function Foo() {
+	return createElement("div", { id: "foo" });
+}
+
+import { createElement } from "not-react";
+function Foo() {
+	return createElement("div", { id: "foo" });
+}
diff --git a/crates/biome_js_analyze/tests/specs/nursery/useUniqueElementIds/valid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/useUniqueElementIds/valid.jsx.snap
new file mode 100644
index 000000000000..a26c320dd702
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/useUniqueElementIds/valid.jsx.snap
@@ -0,0 +1,54 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: valid.jsx
+---
+# Input
+```jsx
+// should not generate diagnostics;
+function Foo() {
+	const id = useId();
+	return <div id={id}></div>;
+}
+
+function Foo() {
+	const id = useId();
+	return (
+		<div id={id}>
+			<div>bar</div>
+		</div>
+	);
+}
+
+function Foo() {
+	const id = useId();
+	return (
+		<div>
+			<div id={id}>bar</div>
+		</div>
+	);
+}
+
+function Foo() {
+	const id = crypto.randomUUID();
+	return <div id={id}></div>;
+}
+
+function Foo({ id }) {
+	return <div id={id}></div>;
+}
+
+function Foo() {
+	const id = useId();
+	return React.createElement("div", { id });
+}
+
+function Foo() {
+	return createElement("div", { id: "foo" });
+}
+
+import { createElement } from "not-react";
+function Foo() {
+	return createElement("div", { id: "foo" });
+}
+
+```
diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts
index 0912adc2e9da..f3e931e5103c 100644
--- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts
+++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts
@@ -1659,6 +1659,10 @@ export interface Nursery {
 	 * Require a description parameter for the Symbol().
 	 */
 	useSymbolDescription?: RuleConfiguration_for_Null;
+	/**
+	 * Prevent the usage of static string literal id attribute on elements.
+	 */
+	useUniqueElementIds?: RuleConfiguration_for_Null;
 }
 /**
  * A list of rules that belong to this group
@@ -3512,6 +3516,7 @@ export type Category =
 	| "lint/nursery/useSortedClasses"
 	| "lint/nursery/useSortedProperties"
 	| "lint/nursery/useSymbolDescription"
+	| "lint/nursery/useUniqueElementIds"
 	| "lint/performance/noAccumulatingSpread"
 	| "lint/performance/noBarrelFile"
 	| "lint/performance/noDelete"
diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json
index fddc58192e82..d5ca878d3356 100644
--- a/packages/@biomejs/biome/configuration_schema.json
+++ b/packages/@biomejs/biome/configuration_schema.json
@@ -2952,6 +2952,13 @@
 						{ "$ref": "#/definitions/RuleConfiguration" },
 						{ "type": "null" }
 					]
+				},
+				"useUniqueElementIds": {
+					"description": "Prevent the usage of static string literal id attribute on elements.",
+					"anyOf": [
+						{ "$ref": "#/definitions/RuleConfiguration" },
+						{ "type": "null" }
+					]
 				}
 			},
 			"additionalProperties": false


Reviews:
user: login: dyc3
body: Could you point this PR to `main` instead of `next`?

To fix the merge conflicts, rebase on `main` and run `just gen-all` to resolve them since those are generated files.

I'm looking into why the rule isn't flagging the other cases in your `invalid` test. Your code _looks_ right so this is a little weird.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T15:17:37Z
user: login: dyc3
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T15:53:14Z
user: login: unvalley
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T18:39:43Z
user: login: unvalley
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T18:44:24Z
user: login: mehm8128
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T22:08:14Z
user: login: dyc3
body: Looks good to me! Fantastic work!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T23:10:58Z
user: login: dyc3
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T23:12:54Z
user: login: mehm8128
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T23:57:10Z
user: login: unvalley
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T03:39:35Z
user: login: dyc3
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T04:28:00Z
user: login: mehm8128
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T06:15:05Z
user: login: ematipico
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T06:25:46Z
user: login: mehm8128
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T12:37:45Z
user: login: arendjr
body: Nice work!

Left two small copy suggestions, but happy as it is!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T12:43:16Z
user: login: mehm8128
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T12:51:26Z
user: login: dyc3
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T13:11:58Z

Review comments:
path: crates/biome_js_analyze/tests/specs/nursery/noStaticElementId/invalid.js
user: login: dyc3
body: Oh I know what's going on. These should be `.jsx` files, not `.js`
created_at: 2025-05-19T15:53:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,121 @@
+use biome_analyze::{Rule, RuleDiagnostic, RuleDomain, context::RuleContext, declare_lint_rule};
+use biome_console::markup;
+use biome_diagnostics::Severity;
+use biome_js_semantic::SemanticModel;
+use biome_js_syntax::{
+    AnyJsExpression, AnyJsxAttributeValue, JsCallExpression, JsPropertyObjectMember, JsxAttribute,
+    jsx_ext::AnyJsxElement,
+};
+use biome_rowan::{AstNode, declare_node_union};
+
+use crate::react::{ReactApiCall, ReactCreateElementCall};
+use crate::services::semantic::Semantic;
+
+declare_lint_rule! {
+    /// Prevent the usage of static string literal `id` attribute on elements.
+    ///
+    /// In React, hardcoding IDs is discouraged because IDs have to be unique in the DOM.
+    /// You should use [`useId`](https://react.dev/reference/react/useId) to generate unique IDs for accessibility purposes.
+    ///
+    /// ## Examples
+    ///
+    /// ### Invalid
+    ///
+    /// ```jsx,expect_diagnostic
+    /// <div id="foo">bar</div>;
+    /// ```
+    ///
+    /// ```jsx,expect_diagnostic
+    /// React.createElement("div", { id: "foo" });
+    /// ```
+    ///
+    /// ### Valid
+    ///
+    /// ```jsx
+    /// const id = useId();
+    /// <div id={id}>bar</div>;
+    /// ```
+    ///
+    /// ```jsx
+    /// const id = useId();
+    /// React.createElement("div", { id });
+    /// ```
+    ///
+    pub NoStaticElementId {
+        version: "next",
+        name: "noStaticElementId",
+        language: "jsx",
+        recommended: true,
+        severity: Severity::Error,
+        domains: &[RuleDomain::React],
+    }
+}
+
+declare_node_union! {
+    pub IdProp = JsxAttribute | JsPropertyObjectMember
+}
+
+declare_node_union! {
+    pub NoStaticElementIdQuery = AnyJsxElement | JsCallExpression
+}
+
+impl NoStaticElementIdQuery {
+    fn find_id_attribute(&self, model: &SemanticModel) -> Option<IdProp> {
+        match self {
+            Self::AnyJsxElement(jsx) => jsx.find_attribute_by_name("id").map(IdProp::from),
+            Self::JsCallExpression(expression) => {
+                let react_create_element =
+                    ReactCreateElementCall::from_call_expression(expression, model)?;
+                react_create_element
+                    .find_prop_by_name("id")
+                    .map(IdProp::from)
+            }
+        }
+    }
+}
+
+impl Rule for NoStaticElementId {
+    type Query = Semantic<NoStaticElementIdQuery>;
+    type State = ();
+    type Signals = Option<Self::State>;
+    type Options = ();
+
+    fn run(ctx: &RuleContext<Self>) -> Self::Signals {
+        let node = ctx.query();
+        let model = ctx.model();
+        let id_attribute = node.find_id_attribute(model)?;
+
+        match id_attribute {
+            IdProp::JsxAttribute(jsx_attribute) => {
+                let jsx_any_attribute_value = jsx_attribute.initializer()?.value().ok()?;
+                if matches!(jsx_any_attribute_value, AnyJsxAttributeValue::JsxString(_)) {
+                    return Some(());
+                }
+                None
+            }
+            IdProp::JsPropertyObjectMember(js_object_member) => {
+                let expression = js_object_member.value().ok()?;
+                if matches!(expression, AnyJsExpression::AnyJsLiteralExpression(_)) {
+                    return Some(());
+                }
+                None
+            }
+        }
+    }
+
+    fn diagnostic(ctx: &RuleContext<Self>, _state: &Self::State) -> Option<RuleDiagnostic> {
+        let node = ctx.query();
+        Some(
+            RuleDiagnostic::new(
+                rule_category!(),
+                node.range(),
+                markup! {
+                    "`id` attribute should not be static string literal. Generate unique IDs using `useId`."
+                },
+            )
+            .note(markup! {
+                "In React, if you hardcode IDs and use the component multiple times, it can lead to duplicate IDs in the DOM. So it's better to generate unique IDs using `useId`."
path: crates/biome_js_analyze/src/lint/nursery/no_static_element_id.rs
user: login: unvalley
body: you can use `<Emphasis>` tag instead of back quotation ` here. please check some other rules
created_at: 2025-05-19T18:39:43Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,24 @@
+---
+"@biomejs/biome": minor
+---
+
+Added the rule [noStaticElementId](https://biomejs.dev/linter/rules/no-static-element-id/).
+This rule disallows the use of static IDs in React components. It encourages to generate unique IDs for accessibility purposes using [`useId`](https://ja.react.dev/reference/react/useId).
path: .changeset/large-crabs-yell.md
user: login: unvalley
body: nits; remove ja
```suggestion
This rule disallows the use of static IDs in React components. It encourages to generate unique IDs for accessibility purposes using [`useId`](https://react.dev/reference/react/useId).
```
created_at: 2025-05-19T18:44:24Z
author_association: MEMBER
path: crates/biome_js_analyze/tests/specs/nursery/noStaticElementId/invalid.js
user: login: mehm8128
body: Thank you! I successfully run it and could show all errors!
created_at: 2025-05-19T22:08:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,121 @@
+use biome_analyze::{Rule, RuleDiagnostic, RuleDomain, context::RuleContext, declare_lint_rule};
+use biome_console::markup;
+use biome_diagnostics::Severity;
+use biome_js_semantic::SemanticModel;
+use biome_js_syntax::{
+    AnyJsExpression, AnyJsxAttributeValue, JsCallExpression, JsPropertyObjectMember, JsxAttribute,
+    jsx_ext::AnyJsxElement,
+};
+use biome_rowan::{AstNode, declare_node_union};
+
+use crate::react::{ReactApiCall, ReactCreateElementCall};
+use crate::services::semantic::Semantic;
+
+declare_lint_rule! {
+    /// Prevent the usage of static string literal `id` attribute on elements.
+    ///
+    /// In React, hardcoding IDs is discouraged because IDs have to be unique in the DOM.
+    /// You should use [`useId`](https://react.dev/reference/react/useId) to generate unique IDs for accessibility purposes.
+    ///
+    /// ## Examples
+    ///
+    /// ### Invalid
+    ///
+    /// ```jsx,expect_diagnostic
+    /// <div id="foo">bar</div>;
+    /// ```
+    ///
+    /// ```jsx,expect_diagnostic
+    /// React.createElement("div", { id: "foo" });
+    /// ```
+    ///
+    /// ### Valid
+    ///
+    /// ```jsx
+    /// const id = useId();
+    /// <div id={id}>bar</div>;
+    /// ```
+    ///
+    /// ```jsx
+    /// const id = useId();
+    /// React.createElement("div", { id });
+    /// ```
+    ///
+    pub NoStaticElementId {
+        version: "next",
path: crates/biome_js_analyze/src/lint/nursery/no_static_element_id.rs
user: login: dyc3
body: Oh actually, we should change this to 2.0.0 because we've already done the rule promotion for the 2.0 release.
created_at: 2025-05-19T23:12:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,121 @@
+use biome_analyze::{Rule, RuleDiagnostic, RuleDomain, context::RuleContext, declare_lint_rule};
+use biome_console::markup;
+use biome_diagnostics::Severity;
+use biome_js_semantic::SemanticModel;
+use biome_js_syntax::{
+    AnyJsExpression, AnyJsxAttributeValue, JsCallExpression, JsPropertyObjectMember, JsxAttribute,
+    jsx_ext::AnyJsxElement,
+};
+use biome_rowan::{AstNode, declare_node_union};
+
+use crate::react::{ReactApiCall, ReactCreateElementCall};
+use crate::services::semantic::Semantic;
+
+declare_lint_rule! {
+    /// Prevent the usage of static string literal `id` attribute on elements.
+    ///
+    /// In React, hardcoding IDs is discouraged because IDs have to be unique in the DOM.
+    /// You should use [`useId`](https://react.dev/reference/react/useId) to generate unique IDs for accessibility purposes.
+    ///
+    /// ## Examples
+    ///
+    /// ### Invalid
+    ///
+    /// ```jsx,expect_diagnostic
+    /// <div id="foo">bar</div>;
+    /// ```
+    ///
+    /// ```jsx,expect_diagnostic
+    /// React.createElement("div", { id: "foo" });
+    /// ```
+    ///
+    /// ### Valid
+    ///
+    /// ```jsx
+    /// const id = useId();
+    /// <div id={id}>bar</div>;
+    /// ```
+    ///
+    /// ```jsx
+    /// const id = useId();
+    /// React.createElement("div", { id });
+    /// ```
+    ///
+    pub NoStaticElementId {
+        version: "next",
path: crates/biome_js_analyze/src/lint/nursery/no_static_element_id.rs
user: login: mehm8128
body: I just fixed it😄
created_at: 2025-05-19T23:57:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,121 @@
+use biome_analyze::{Rule, RuleDiagnostic, RuleDomain, context::RuleContext, declare_lint_rule};
+use biome_console::markup;
+use biome_diagnostics::Severity;
+use biome_js_semantic::SemanticModel;
+use biome_js_syntax::{
+    AnyJsExpression, AnyJsxAttributeValue, JsCallExpression, JsPropertyObjectMember, JsxAttribute,
+    jsx_ext::AnyJsxElement,
+};
+use biome_rowan::{AstNode, declare_node_union};
+
+use crate::react::{ReactApiCall, ReactCreateElementCall};
+use crate::services::semantic::Semantic;
+
+declare_lint_rule! {
+    /// Prevent the usage of static string literal `id` attribute on elements.
+    ///
+    /// In React, hardcoding IDs is discouraged because IDs have to be unique in the DOM.
+    /// You should use [`useId`](https://react.dev/reference/react/useId) to generate unique IDs for accessibility purposes.
+    ///
+    /// ## Examples
+    ///
+    /// ### Invalid
+    ///
+    /// ```jsx,expect_diagnostic
+    /// <div id="foo">bar</div>;
+    /// ```
+    ///
+    /// ```jsx,expect_diagnostic
+    /// React.createElement("div", { id: "foo" });
+    /// ```
+    ///
+    /// ### Valid
+    ///
+    /// ```jsx
+    /// const id = useId();
+    /// <div id={id}>bar</div>;
+    /// ```
+    ///
+    /// ```jsx
+    /// const id = useId();
+    /// React.createElement("div", { id });
+    /// ```
+    ///
+    pub NoStaticElementId {
+        version: "2.0.0",
+        name: "noStaticElementId",
+        language: "jsx",
+        recommended: true,
path: crates/biome_js_analyze/src/lint/nursery/no_static_element_id.rs
user: login: unvalley
body: I’m a bit hesitant to set recommended: true from the beginning. I'm not sure this rule is for every react users.
@dyc3 @mehm8128  what do you think?
created_at: 2025-05-20T03:39:35Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,121 @@
+use biome_analyze::{Rule, RuleDiagnostic, RuleDomain, context::RuleContext, declare_lint_rule};
+use biome_console::markup;
+use biome_diagnostics::Severity;
+use biome_js_semantic::SemanticModel;
+use biome_js_syntax::{
+    AnyJsExpression, AnyJsxAttributeValue, JsCallExpression, JsPropertyObjectMember, JsxAttribute,
+    jsx_ext::AnyJsxElement,
+};
+use biome_rowan::{AstNode, declare_node_union};
+
+use crate::react::{ReactApiCall, ReactCreateElementCall};
+use crate::services::semantic::Semantic;
+
+declare_lint_rule! {
+    /// Prevent the usage of static string literal `id` attribute on elements.
+    ///
+    /// In React, hardcoding IDs is discouraged because IDs have to be unique in the DOM.
+    /// You should use [`useId`](https://react.dev/reference/react/useId) to generate unique IDs for accessibility purposes.
+    ///
+    /// ## Examples
+    ///
+    /// ### Invalid
+    ///
+    /// ```jsx,expect_diagnostic
+    /// <div id="foo">bar</div>;
+    /// ```
+    ///
+    /// ```jsx,expect_diagnostic
+    /// React.createElement("div", { id: "foo" });
+    /// ```
+    ///
+    /// ### Valid
+    ///
+    /// ```jsx
+    /// const id = useId();
+    /// <div id={id}>bar</div>;
+    /// ```
+    ///
+    /// ```jsx
+    /// const id = useId();
+    /// React.createElement("div", { id });
+    /// ```
+    ///
+    pub NoStaticElementId {
+        version: "2.0.0",
+        name: "noStaticElementId",
+        language: "jsx",
+        recommended: true,
path: crates/biome_js_analyze/src/lint/nursery/no_static_element_id.rs
user: login: dyc3
body: Tbh, I'm not really in that many react code bases. So I don't really have a sense for what the average react code base is like.

To be safe, it would probably be better if it was not recommended by default. I can think of plenty of reasons for why a legacy code base would have static IDs for various things. We can always make it recommended later.
created_at: 2025-05-20T04:28:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,121 @@
+use biome_analyze::{Rule, RuleDiagnostic, RuleDomain, context::RuleContext, declare_lint_rule};
+use biome_console::markup;
+use biome_diagnostics::Severity;
+use biome_js_semantic::SemanticModel;
+use biome_js_syntax::{
+    AnyJsExpression, AnyJsxAttributeValue, JsCallExpression, JsPropertyObjectMember, JsxAttribute,
+    jsx_ext::AnyJsxElement,
+};
+use biome_rowan::{AstNode, declare_node_union};
+
+use crate::react::{ReactApiCall, ReactCreateElementCall};
+use crate::services::semantic::Semantic;
+
+declare_lint_rule! {
+    /// Prevent the usage of static string literal `id` attribute on elements.
+    ///
+    /// In React, hardcoding IDs is discouraged because IDs have to be unique in the DOM.
+    /// You should use [`useId`](https://react.dev/reference/react/useId) to generate unique IDs for accessibility purposes.
+    ///
+    /// ## Examples
+    ///
+    /// ### Invalid
+    ///
+    /// ```jsx,expect_diagnostic
+    /// <div id="foo">bar</div>;
+    /// ```
+    ///
+    /// ```jsx,expect_diagnostic
+    /// React.createElement("div", { id: "foo" });
+    /// ```
+    ///
+    /// ### Valid
+    ///
+    /// ```jsx
+    /// const id = useId();
+    /// <div id={id}>bar</div>;
+    /// ```
+    ///
+    /// ```jsx
+    /// const id = useId();
+    /// React.createElement("div", { id });
+    /// ```
+    ///
+    pub NoStaticElementId {
+        version: "2.0.0",
+        name: "noStaticElementId",
+        language: "jsx",
+        recommended: true,
path: crates/biome_js_analyze/src/lint/nursery/no_static_element_id.rs
user: login: mehm8128
body: Thank you, I agree with you. I'll change it to `recommended: false`.
created_at: 2025-05-20T06:15:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,37 @@
+// should not generate diagnostics;
+function Foo() {
+	const id = useId();
+	return <div id={id}></div>;
+}
+
+function Foo() {
+	const id = useId();
+	return (
+		<div id={id}>
+			<div>bar</div>
+		</div>
+	);
+}
+
+function Foo() {
+	const id = useId();
+	return (
+		<div>
+			<div id={id}>bar</div>
+		</div>
+	);
+}
+
+function Foo() {
+	const id = crypto.randomUUID();
+	return <div id={id}></div>;
+}
+
+function Foo({ id }) {
+	return <div id={id}></div>;
+}
+
+function Foo() {
+	const id = useId();
+	return React.createElement("div", { id });
+}
path: crates/biome_js_analyze/tests/specs/nursery/noStaticElementId/valid.jsx
user: login: ematipico
body: Can we add more test cases? For example, the following snippet shouldn't be triggered 

```js
createSomething("div", id)
```

This snippet should trigger the rule 
```js
import {createElement} from "react"
createElement("div", id)
```
Also, replacing `"react"` with something else shouldn't trigger the rule 
created_at: 2025-05-20T06:25:40Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,37 @@
+// should not generate diagnostics;
+function Foo() {
+	const id = useId();
+	return <div id={id}></div>;
+}
+
+function Foo() {
+	const id = useId();
+	return (
+		<div id={id}>
+			<div>bar</div>
+		</div>
+	);
+}
+
+function Foo() {
+	const id = useId();
+	return (
+		<div>
+			<div id={id}>bar</div>
+		</div>
+	);
+}
+
+function Foo() {
+	const id = crypto.randomUUID();
+	return <div id={id}></div>;
+}
+
+function Foo({ id }) {
+	return <div id={id}></div>;
+}
+
+function Foo() {
+	const id = useId();
+	return React.createElement("div", { id });
+}
path: crates/biome_js_analyze/tests/specs/nursery/noStaticElementId/valid.jsx
user: login: mehm8128
body: Thank you, I overlooked it. I added the test cases: https://github.com/biomejs/biome/pull/6082/commits/63f339149aaa630e7bc4512eb0d034069fcb81ce
created_at: 2025-05-20T12:37:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,123 @@
+use biome_analyze::{Rule, RuleDiagnostic, RuleDomain, context::RuleContext, declare_lint_rule};
+use biome_console::markup;
+use biome_diagnostics::Severity;
+use biome_js_semantic::SemanticModel;
+use biome_js_syntax::{
+    AnyJsExpression, AnyJsxAttributeValue, JsCallExpression, JsPropertyObjectMember, JsxAttribute,
+    jsx_ext::AnyJsxElement,
+};
+use biome_rowan::{AstNode, declare_node_union};
+
+use crate::react::{ReactApiCall, ReactCreateElementCall};
+use crate::services::semantic::Semantic;
+
+declare_lint_rule! {
+    /// Prevent the usage of static string literal `id` attribute on elements.
+    ///
+    /// In React, hardcoding IDs is discouraged because IDs have to be unique in the DOM.
+    /// You should use [`useId`](https://react.dev/reference/react/useId) to generate unique IDs for accessibility purposes.
+    ///
+    /// Please keep in mind this rule doesn't check whether ids are actually unique or not, and does check whether static literal id isn't passed to the elements or not. So you're encouraged to check by yourself if the ids are actually unique.
+    ///
+    /// ## Examples
+    ///
+    /// ### Invalid
+    ///
+    /// ```jsx,expect_diagnostic
+    /// <div id="foo">bar</div>;
+    /// ```
+    ///
+    /// ```jsx,expect_diagnostic
+    /// React.createElement("div", { id: "foo" });
+    /// ```
+    ///
+    /// ### Valid
+    ///
+    /// ```jsx
+    /// const id = useId();
+    /// <div id={id}>bar</div>;
+    /// ```
+    ///
+    /// ```jsx
+    /// const id = useId();
+    /// React.createElement("div", { id });
+    /// ```
+    ///
+    pub UseUniqueElementIds {
+        version: "2.0.0",
+        name: "useUniqueElementIds",
+        language: "jsx",
+        recommended: false,
+        severity: Severity::Error,
+        domains: &[RuleDomain::React],
+    }
+}
+
+declare_node_union! {
+    pub IdProp = JsxAttribute | JsPropertyObjectMember
+}
+
+declare_node_union! {
+    pub UseUniqueElementIdsQuery = AnyJsxElement | JsCallExpression
+}
+
+impl UseUniqueElementIdsQuery {
+    fn find_id_attribute(&self, model: &SemanticModel) -> Option<IdProp> {
+        match self {
+            Self::AnyJsxElement(jsx) => jsx.find_attribute_by_name("id").map(IdProp::from),
+            Self::JsCallExpression(expression) => {
+                let react_create_element =
+                    ReactCreateElementCall::from_call_expression(expression, model)?;
+                react_create_element
+                    .find_prop_by_name("id")
+                    .map(IdProp::from)
+            }
+        }
+    }
+}
+
+impl Rule for UseUniqueElementIds {
+    type Query = Semantic<UseUniqueElementIdsQuery>;
+    type State = ();
+    type Signals = Option<Self::State>;
+    type Options = ();
+
+    fn run(ctx: &RuleContext<Self>) -> Self::Signals {
+        let node = ctx.query();
+        let model = ctx.model();
+        let id_attribute = node.find_id_attribute(model)?;
+
+        match id_attribute {
+            IdProp::JsxAttribute(jsx_attribute) => {
+                let jsx_any_attribute_value = jsx_attribute.initializer()?.value().ok()?;
+                if matches!(jsx_any_attribute_value, AnyJsxAttributeValue::JsxString(_)) {
+                    return Some(());
+                }
+                None
+            }
+            IdProp::JsPropertyObjectMember(js_object_member) => {
+                let expression = js_object_member.value().ok()?;
+                if matches!(expression, AnyJsExpression::AnyJsLiteralExpression(_)) {
+                    return Some(());
+                }
+                None
+            }
+        }
+    }
+
+    fn diagnostic(ctx: &RuleContext<Self>, _state: &Self::State) -> Option<RuleDiagnostic> {
+        let node = ctx.query();
+        Some(
+            RuleDiagnostic::new(
+                rule_category!(),
+                node.range(),
+                markup! {
+                    <Emphasis>"id"</Emphasis>" attribute should not be static string literal. Generate unique IDs using "<Emphasis>"useId"</Emphasis>"."
path: crates/biome_js_analyze/src/lint/nursery/use_unique_element_ids.rs
user: login: arendjr
body: ```suggestion
                    <Emphasis>"id"</Emphasis>" attribute should not be a static string literal. Generate unique IDs using "<Emphasis>"useId()"</Emphasis>"."
```
created_at: 2025-05-22T12:41:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,123 @@
+use biome_analyze::{Rule, RuleDiagnostic, RuleDomain, context::RuleContext, declare_lint_rule};
+use biome_console::markup;
+use biome_diagnostics::Severity;
+use biome_js_semantic::SemanticModel;
+use biome_js_syntax::{
+    AnyJsExpression, AnyJsxAttributeValue, JsCallExpression, JsPropertyObjectMember, JsxAttribute,
+    jsx_ext::AnyJsxElement,
+};
+use biome_rowan::{AstNode, declare_node_union};
+
+use crate::react::{ReactApiCall, ReactCreateElementCall};
+use crate::services::semantic::Semantic;
+
+declare_lint_rule! {
+    /// Prevent the usage of static string literal `id` attribute on elements.
+    ///
+    /// In React, hardcoding IDs is discouraged because IDs have to be unique in the DOM.
+    /// You should use [`useId`](https://react.dev/reference/react/useId) to generate unique IDs for accessibility purposes.
+    ///
+    /// Please keep in mind this rule doesn't check whether ids are actually unique or not, and does check whether static literal id isn't passed to the elements or not. So you're encouraged to check by yourself if the ids are actually unique.
+    ///
+    /// ## Examples
+    ///
+    /// ### Invalid
+    ///
+    /// ```jsx,expect_diagnostic
+    /// <div id="foo">bar</div>;
+    /// ```
+    ///
+    /// ```jsx,expect_diagnostic
+    /// React.createElement("div", { id: "foo" });
+    /// ```
+    ///
+    /// ### Valid
+    ///
+    /// ```jsx
+    /// const id = useId();
+    /// <div id={id}>bar</div>;
+    /// ```
+    ///
+    /// ```jsx
+    /// const id = useId();
+    /// React.createElement("div", { id });
+    /// ```
+    ///
+    pub UseUniqueElementIds {
+        version: "2.0.0",
+        name: "useUniqueElementIds",
+        language: "jsx",
+        recommended: false,
+        severity: Severity::Error,
+        domains: &[RuleDomain::React],
+    }
+}
+
+declare_node_union! {
+    pub IdProp = JsxAttribute | JsPropertyObjectMember
+}
+
+declare_node_union! {
+    pub UseUniqueElementIdsQuery = AnyJsxElement | JsCallExpression
+}
+
+impl UseUniqueElementIdsQuery {
+    fn find_id_attribute(&self, model: &SemanticModel) -> Option<IdProp> {
+        match self {
+            Self::AnyJsxElement(jsx) => jsx.find_attribute_by_name("id").map(IdProp::from),
+            Self::JsCallExpression(expression) => {
+                let react_create_element =
+                    ReactCreateElementCall::from_call_expression(expression, model)?;
+                react_create_element
+                    .find_prop_by_name("id")
+                    .map(IdProp::from)
+            }
+        }
+    }
+}
+
+impl Rule for UseUniqueElementIds {
+    type Query = Semantic<UseUniqueElementIdsQuery>;
+    type State = ();
+    type Signals = Option<Self::State>;
+    type Options = ();
+
+    fn run(ctx: &RuleContext<Self>) -> Self::Signals {
+        let node = ctx.query();
+        let model = ctx.model();
+        let id_attribute = node.find_id_attribute(model)?;
+
+        match id_attribute {
+            IdProp::JsxAttribute(jsx_attribute) => {
+                let jsx_any_attribute_value = jsx_attribute.initializer()?.value().ok()?;
+                if matches!(jsx_any_attribute_value, AnyJsxAttributeValue::JsxString(_)) {
+                    return Some(());
+                }
+                None
+            }
+            IdProp::JsPropertyObjectMember(js_object_member) => {
+                let expression = js_object_member.value().ok()?;
+                if matches!(expression, AnyJsExpression::AnyJsLiteralExpression(_)) {
+                    return Some(());
+                }
+                None
+            }
+        }
+    }
+
+    fn diagnostic(ctx: &RuleContext<Self>, _state: &Self::State) -> Option<RuleDiagnostic> {
+        let node = ctx.query();
+        Some(
+            RuleDiagnostic::new(
+                rule_category!(),
+                node.range(),
+                markup! {
+                    <Emphasis>"id"</Emphasis>" attribute should not be static string literal. Generate unique IDs using "<Emphasis>"useId"</Emphasis>"."
+                },
+            )
+            .note(markup! {
+                "In React, if you hardcode IDs and use the component multiple times, it can lead to duplicate IDs in the DOM. So it's better to generate unique IDs using "<Emphasis>"useId"</Emphasis>"."
path: crates/biome_js_analyze/src/lint/nursery/use_unique_element_ids.rs
user: login: arendjr
body: ```suggestion
                "In React, if you hardcode IDs and use the component multiple times, it can lead to duplicate IDs in the DOM. Instead, generate unique IDs using "<Emphasis>"useId()"</Emphasis>"."
```
created_at: 2025-05-22T12:42:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,123 @@
+use biome_analyze::{Rule, RuleDiagnostic, RuleDomain, context::RuleContext, declare_lint_rule};
+use biome_console::markup;
+use biome_diagnostics::Severity;
+use biome_js_semantic::SemanticModel;
+use biome_js_syntax::{
+    AnyJsExpression, AnyJsxAttributeValue, JsCallExpression, JsPropertyObjectMember, JsxAttribute,
+    jsx_ext::AnyJsxElement,
+};
+use biome_rowan::{AstNode, declare_node_union};
+
+use crate::react::{ReactApiCall, ReactCreateElementCall};
+use crate::services::semantic::Semantic;
+
+declare_lint_rule! {
+    /// Prevent the usage of static string literal `id` attribute on elements.
+    ///
+    /// In React, hardcoding IDs is discouraged because IDs have to be unique in the DOM.
+    /// You should use [`useId`](https://react.dev/reference/react/useId) to generate unique IDs for accessibility purposes.
+    ///
+    /// Please keep in mind this rule doesn't check whether ids are actually unique or not, and does check whether static literal id isn't passed to the elements or not. So you're encouraged to check by yourself if the ids are actually unique.
+    ///
+    /// ## Examples
+    ///
+    /// ### Invalid
+    ///
+    /// ```jsx,expect_diagnostic
+    /// <div id="foo">bar</div>;
+    /// ```
+    ///
+    /// ```jsx,expect_diagnostic
+    /// React.createElement("div", { id: "foo" });
+    /// ```
+    ///
+    /// ### Valid
+    ///
+    /// ```jsx
+    /// const id = useId();
+    /// <div id={id}>bar</div>;
+    /// ```
+    ///
+    /// ```jsx
+    /// const id = useId();
+    /// React.createElement("div", { id });
+    /// ```
+    ///
+    pub UseUniqueElementIds {
+        version: "2.0.0",
+        name: "useUniqueElementIds",
+        language: "jsx",
+        recommended: false,
+        severity: Severity::Error,
+        domains: &[RuleDomain::React],
+    }
+}
+
+declare_node_union! {
+    pub IdProp = JsxAttribute | JsPropertyObjectMember
+}
+
+declare_node_union! {
+    pub UseUniqueElementIdsQuery = AnyJsxElement | JsCallExpression
+}
+
+impl UseUniqueElementIdsQuery {
+    fn find_id_attribute(&self, model: &SemanticModel) -> Option<IdProp> {
+        match self {
+            Self::AnyJsxElement(jsx) => jsx.find_attribute_by_name("id").map(IdProp::from),
+            Self::JsCallExpression(expression) => {
+                let react_create_element =
+                    ReactCreateElementCall::from_call_expression(expression, model)?;
+                react_create_element
+                    .find_prop_by_name("id")
+                    .map(IdProp::from)
+            }
+        }
+    }
+}
+
+impl Rule for UseUniqueElementIds {
+    type Query = Semantic<UseUniqueElementIdsQuery>;
+    type State = ();
+    type Signals = Option<Self::State>;
+    type Options = ();
+
+    fn run(ctx: &RuleContext<Self>) -> Self::Signals {
+        let node = ctx.query();
+        let model = ctx.model();
+        let id_attribute = node.find_id_attribute(model)?;
+
+        match id_attribute {
+            IdProp::JsxAttribute(jsx_attribute) => {
+                let jsx_any_attribute_value = jsx_attribute.initializer()?.value().ok()?;
+                if matches!(jsx_any_attribute_value, AnyJsxAttributeValue::JsxString(_)) {
+                    return Some(());
+                }
+                None
+            }
+            IdProp::JsPropertyObjectMember(js_object_member) => {
+                let expression = js_object_member.value().ok()?;
+                if matches!(expression, AnyJsExpression::AnyJsLiteralExpression(_)) {
+                    return Some(());
+                }
+                None
+            }
+        }
+    }
+
+    fn diagnostic(ctx: &RuleContext<Self>, _state: &Self::State) -> Option<RuleDiagnostic> {
+        let node = ctx.query();
+        Some(
+            RuleDiagnostic::new(
+                rule_category!(),
+                node.range(),
+                markup! {
+                    <Emphasis>"id"</Emphasis>" attribute should not be static string literal. Generate unique IDs using "<Emphasis>"useId"</Emphasis>"."
+                },
+            )
+            .note(markup! {
+                "In React, if you hardcode IDs and use the component multiple times, it can lead to duplicate IDs in the DOM. So it's better to generate unique IDs using "<Emphasis>"useId"</Emphasis>"."
path: crates/biome_js_analyze/src/lint/nursery/use_unique_element_ids.rs
user: login: mehm8128
body: Thank you, I applied them!
created_at: 2025-05-22T12:51:26Z
author_association: CONTRIBUTOR

PR comments:
user: login: mehm8128
created_at: 2025-05-19T22:11:58Z
body: @dyc3 @unvalley 
Thank you for reviewing! I fixed all the pointed out thing and also successfully updated snapshot file and resolved conflict!
author_association: CONTRIBUTOR
user: login: codspeed-hq[bot]
created_at: 2025-05-20T00:30:41Z
body: <!-- __CODSPEED_PERFORMANCE_REPORT_COMMENT__ -->
<!-- __CODSPEED_INSTRUMENTATION_PERFORMANCE_REPORT_COMMENT__ -->

## [CodSpeed Performance Report](https://codspeed.io/biomejs/biome/branches/mehm8128%3Afeat%2Fimpl-noStaticElementId)

### Merging #6082 will **not alter performance**

<sub>Comparing <code>mehm8128:feat/impl-noStaticElementId</code> (f409fce) with <code>main</code> (efa31e5)</sub>



### Summary

`✅ 95` untouched benchmarks  




author_association: NONE
user: login: unvalley
created_at: 2025-05-20T06:27:40Z
body: Sorry for the intermittent reviews.

Regarding the name of the rule, what about changing it to something like `noStaticLiteralId`?

We have a lint rule like this: https://biomejs.dev/linter/rules/no-static-element-interactions/
In the context of a11y, the term `static elements` refers to `div`, `span`, etc. This PR lint rule is also relevant to a11y, but I think it is more concerned with id attributes rather than HTML elements.
author_association: MEMBER
user: login: arendjr
created_at: 2025-05-20T07:15:46Z
body: > but I think it is more concerned with id attributes rather than HTML elements

Aren’t those id attributes set on HTML elements? With `noStaticElementId` I immediately understand what this rule is about, but `noStaticLiteralId` just makes it less clear to me.
author_association: CONTRIBUTOR
user: login: mehm8128
created_at: 2025-05-20T12:30:14Z
body: I think what unvalley want to tell is which word is described with "static" word "element" or "id". In `noStaticElementInteractions` rule, "static" describes "element" because this rule focuses **static element**, which is not interactive (interactive element means `<a>`, `<button>`, and so on), but this time "static" of `noStaticElementId` means **"id is static"** in the sense that "id is hardcoded". So I think at the point of consistency against `noStaticElementInteractions`, `noStaticElementId` is a little confusing.
author_association: CONTRIBUTOR
user: login: mehm8128
created_at: 2025-05-20T12:51:07Z
body: I prefer to name it "noStaticIdToElement" even though it is somewhat redundant. There are other rules whose name includes `To` word and I think this name make it more clear what this rule prevents.
author_association: CONTRIBUTOR
user: login: unvalley
created_at: 2025-05-20T12:56:40Z
body: > Aren’t those id attributes set on HTML elements?

Yes. 

I think the name of the rule should be less misleading. So I was going to say that it looks fine as it is, `noStaticIdToElement` by mehm8128 sounds straightforward. What’s your take? @arendjr @dyc3 
author_association: MEMBER
user: login: arendjr
created_at: 2025-05-20T13:47:25Z
body: Hm, how about `useUniqueElementIds` or even `useGloballyUniqueElementIds` then? No confusion around the word "static" and I think it's also immediately clear why `useId()` is the right solution for the diagnostic.
author_association: CONTRIBUTOR
user: login: mehm8128
created_at: 2025-05-20T14:04:33Z
body: I think `useUniqueElementId` represents explicitly what is this rule's purpose, but it doesn't represent actually what this rule is checking (because for now this rule is just checking whether provided `id` value is static literal value or not, not checking whether each id is unique). Is it OK to set such name?
author_association: CONTRIBUTOR
user: login: arendjr
created_at: 2025-05-21T07:22:53Z
body: > Is it OK to set such name?

I'd be fine with it :)

Often intention is more important than the specific details of how something is implemented. As long as the docs explain the limitations, I think it's enough.
author_association: CONTRIBUTOR
user: login: mehm8128
created_at: 2025-05-21T13:27:48Z
body: I understand, thank you!
Considering that, I prefer naming it as `useUniqueElementIds`. If no one has more suggestion, I'll rename the rule to it.

I'd like you to teach me how to rename rule. Is there a quick way to rename rule other than re-generating some files with `just new-js-lintrule useMyRuleName`?
author_association: CONTRIBUTOR
user: login: dyc3
created_at: 2025-05-21T15:38:34Z
body: The easiest way to rename a rule I'm aware of is: 
- create a new rule, 
- copy everything over, 
- delete the old one
- and rerun codegen
author_association: CONTRIBUTOR
user: login: mehm8128
created_at: 2025-05-22T12:22:22Z
body: Thank you! I renamed the rule name and added docs about the check limitations. Please re-review it🙏
author_association: CONTRIBUTOR
user: login: arendjr
created_at: 2025-05-22T14:11:23Z
body: Thank you!
author_association: CONTRIBUTOR