diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4496cf9df8..1572345855 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
* [`no-unknown-property`]: Mark onLoad/onError as supported on iframes ([#3398][] @maiis, [#3406][] @akx)
* [`no-unknown-property`]: allow `imageSrcSet` and `imageSizes` attributes on ` ` ([#3407][] @terrymun)
* [`no-unknown-property`]: add `border`; `focusable` on `` ([#3404][] [#3404][] @ljharb)
+* [`no-unknown-property`]: React lowercases `data-` attrs ([#3395][] @ljharb)
[#3407]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3407
[#3406]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3406
@@ -24,6 +25,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
[#3398]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3398
[#3397]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3397
[#3396]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3396
+[#3395]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3395
[#3394]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3394
[#3391]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3391
diff --git a/lib/rules/no-unknown-property.js b/lib/rules/no-unknown-property.js
index f353fceca5..b6f1ff816a 100644
--- a/lib/rules/no-unknown-property.js
+++ b/lib/rules/no-unknown-property.js
@@ -388,15 +388,15 @@ function isValidHTMLTagInJSX(childNode) {
/**
* Checks if an attribute name is a valid `data-*` attribute:
- * if the name starts with "data-" and has some lowcase (a to z) words that can contain numbers, separated but hyphens (-)
- * (which is also called "kebab case" or "dash case"), then the attribute is valid data attribute.
+ * if the name starts with "data-" and has alphanumeric words (browsers require lowercase, but React and TS lowercase them),
+ * not start with any casing of "xml", and separated by hyphens (-) (which is also called "kebab case" or "dash case"),
+ * then the attribute is a valid data attribute.
*
* @param {String} name - Attribute name to be tested
* @returns {boolean} Result
*/
function isValidDataAttribute(name) {
- const dataAttrConvention = /^data(-[a-z1-9]*)*$/;
- return !!dataAttrConvention.test(name);
+ return /^data(-[^:]*)*$/.test(name) && !/^data-xml/i.test(name);
}
/**
diff --git a/tests/lib/rules/no-unknown-property.js b/tests/lib/rules/no-unknown-property.js
index 03feec6130..82e7b50c79 100644
--- a/tests/lib/rules/no-unknown-property.js
+++ b/tests/lib/rules/no-unknown-property.js
@@ -91,6 +91,7 @@ ruleTester.run('no-unknown-property', rule, {
{ code: '
;' },
{ code: '
;' },
{ code: '
;' },
+ { code: '
;' },
// Ignoring should work
{
code: '
;',
@@ -511,5 +512,16 @@ ruleTester.run('no-unknown-property', rule, {
},
],
},
+ {
+ code: '
',
+ errors: [
+ {
+ messageId: 'unknownProp',
+ data: {
+ name: 'data-xml-anything',
+ },
+ },
+ ],
+ },
]),
});