Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion lib/core/utils/is-shadow-root.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,26 @@ const possibleShadowRoots = [
'section',
'span'
];

// Reserved names that match the custom element regex but are not valid custom elements.
// https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
const reservedNames = [
'annotation-xml',
'color-profile',
'font-face',
'font-face-src',
'font-face-uri',
'font-face-format',
'font-face-name',
'missing-glyph'
];

// Spec-compliant PCEN (Potential Custom Element Name) regex.
// Extends the ASCII-only regex to include Unicode characters permitted by the spec.
// https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
const customElementRegex =
/^[a-z](?:[a-z0-9._-]|[\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD])*-(?:[a-z0-9._-]|[\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD])*$/;
Copy link
Copy Markdown
Contributor

@straker straker Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the custom element name spec, the name must be a valid element local name, which the spec gives a regex as /^(?:[A-Za-z][^\0\t\n\f\r\u0020/>]*|[:_\u0080-\u{10FFFF}][A-Za-z0-9-.:_\u0080-\u{10FFFF}]*)$/u. I assume we could use that with a modification to match the custom element spec of requiring an alpha lower for the 0th char, no alpha upper chars, and a hyphen. Wouldn't this regex work? /^[a-z][a-z0-9-.:_\u0080-\u{10FFFF}]*-[a-z0-9-.:_\u0080-\u{10FFFF}]*$/u

Tested that customElements.define('c-') is a valid custom element name which means we don't need anything after the hyphen either.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also add a link to the valid element local name spec as well

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks yeah I'll take a look a little later this evening and read through your suggestions and update the pr to match accordingly


/**
* Test a node to see if it has a spec-conforming shadow root
*
Expand All @@ -29,7 +49,7 @@ function isShadowRoot(node) {
const nodeName = node.nodeName.toLowerCase();
if (
possibleShadowRoots.includes(nodeName) ||
/^[a-z][a-z0-9_.-]*-[a-z0-9_.-]*$/.test(nodeName)
(!reservedNames.includes(nodeName) && customElementRegex.test(nodeName))
) {
return true;
}
Expand Down
12 changes: 12 additions & 0 deletions test/core/utils/is-shadow-root.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ describe('axe.utils.isShadowRoot', function () {
assert.isFalse(isShadowRoot({ nodeName: '0-BUZZ', shadowRoot: {} }));
assert.isFalse(isShadowRoot({ nodeName: '--ELM--', shadowRoot: {} }));
});
it('returns false for reserved custom element names', function () {
assert.isFalse(
isShadowRoot({ nodeName: 'ANNOTATION-XML', shadowRoot: {} })
);
assert.isFalse(isShadowRoot({ nodeName: 'COLOR-PROFILE', shadowRoot: {} }));
assert.isFalse(isShadowRoot({ nodeName: 'FONT-FACE', shadowRoot: {} }));
assert.isFalse(isShadowRoot({ nodeName: 'MISSING-GLYPH', shadowRoot: {} }));
});
it('returns true for Unicode custom element names', function () {
assert.isTrue(isShadowRoot({ nodeName: 'CAF\u00C9-MENU', shadowRoot: {} }));
assert.isTrue(isShadowRoot({ nodeName: 'MATH-\u03A0', shadowRoot: {} }));
});
it('returns false if the native element does not allow shadow DOM', function () {
assert.isFalse(isShadowRoot({ nodeName: 'IFRAME', shadowRoot: {} }));
assert.isFalse(isShadowRoot({ nodeName: 'STRONG', shadowRoot: {} }));
Expand Down