{"id":10479,"date":"2026-07-30T09:43:28","date_gmt":"2026-07-30T14:43:28","guid":{"rendered":"https:\/\/master.dev\/blog\/?p=10479"},"modified":"2026-07-30T09:43:29","modified_gmt":"2026-07-30T14:43:29","slug":"same-name-different-component-with-scoped-custom-element-registries","status":"publish","type":"post","link":"https:\/\/master.dev\/blog\/same-name-different-component-with-scoped-custom-element-registries\/","title":{"rendered":"Same Name; Different Component with Scoped Custom Element Registries"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/developer.chrome.com\/release-notes\/146\">Chrome 146<\/a> recently introduced <a href=\"https:\/\/developer.chrome.com\/release-notes\/146#scoped_custom_element_registry\">scoped custom element registry<\/a>, which is a way to introduce custom elements in your app without using the global registry namespace. This makes it possible for different versions or entirely different custom elements to share the same tag name. <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Firefox\/Releases\/150#experimental_web_features\">Firefox 150<\/a> also has this feature locked behind a feature flag, so it\u2019s likely to be one of the features listed in <a href=\"https:\/\/web.dev\/baseline\/2026\">Baseline 2026<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In this article, I\u2019m going to share one or two examples of components that <em>&#8220;ought to have the same tag name&#8221;<\/em>. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Basics<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">You&#8217;ve got a custom element button. It&#8217;s from your design system, hence the &#8220;ds&#8221; naming:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ds-button<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Great. But maybe you&#8217;re <em>also<\/em> using <em>another<\/em> set of custom components, and it&#8217;s <em>also<\/em> got a <code>&lt;ds-button><\/code>. Or, perhaps more likely, it&#8217;s your own set of custom elements, but you&#8217;ve versioned them, and you need to use v1 and v2 on the same page.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">body<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">main<\/span>&gt;<\/span>\n      <span class=\"hljs-comment\">&lt;!-- v1 usage up here --&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ds-button<\/span>&gt;<\/span>Button<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ds-button<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">main<\/span>&gt;<\/span>\n\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">footer<\/span>&gt;<\/span>\n      <span class=\"hljs-comment\">&lt;!-- v2 usage down here, shadow DOM --&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">footer<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">So maybe our default registry is v1, and we get ready by having a custom registry where we&#8217;re gonna put v2 stuff.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">customElements.define(<span class=\"hljs-string\">\"ds-button\"<\/span>, v1Button);\n\n<span class=\"hljs-keyword\">const<\/span> footerRegistry = <span class=\"hljs-keyword\">new<\/span> CustomElementRegistry();\nfooterRegistry.define(<span class=\"hljs-string\">\"ds-button\"<\/span>, v2Button);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Then <em>one<\/em> way to use the custom registry is to tell the Shadow DOM we put there to use it. This isn&#8217;t required as we&#8217;ll see later, but it&#8217;s one way.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> footer = <span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">\"footer\"<\/span>);\n<span class=\"hljs-keyword\">const<\/span> shadow = footer.attachShadow({\n  <span class=\"hljs-attr\">mode<\/span>: <span class=\"hljs-string\">\"open\"<\/span>,\n  <span class=\"hljs-attr\">customElementRegistry<\/span>: footerRegistry\n});\n\nshadow.innerHTML = <span class=\"hljs-string\">`\n  &lt;ds-button&gt;Will be v2Button!&lt;\/ds-button&gt;\n`<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_019f999d-7e7e-7a38-b344-0c035875145f\" src=\"\/\/codepen.io\/editor\/anon\/embed\/019f999d-7e7e-7a38-b344-0c035875145f?height=450&amp;theme-id=1&amp;slug-hash=019f999d-7e7e-7a38-b344-0c035875145f&amp;default-tab=js,result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed 019f999d-7e7e-7a38-b344-0c035875145f\" title=\"CodePen Embed 019f999d-7e7e-7a38-b344-0c035875145f\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">This is Niche Stuff! <\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Before I proceed, I would like to note that I believe this specific issue or scenario can be considered <em>sub-niche<\/em>. Most readers of this article, regardless of their skill level in front-end programming, are likely to agree.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Custom elements are already their own niche, since most applications these days are built with frameworks like React. I do think, though, if you develop applications with a framework that <em>does compile down to custom elements <\/em>such as <a href=\"https:\/\/lit.dev\/\">Lit<\/a>, you count as a user of this \u2018niche\u2019 feature. Regardless of how widely this feature will be adopted across many websites, it\u2019s already well on its way to becoming a web standard. At the very least, this means you wouldn\u2019t have to worry about this feature being deprecated anytime soon.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">About the Following Demos<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">I used AI to help me create the components by sending images of what the components looked like through the prompt. I also drew ideas from existing design systems, since that\u2019s the easiest way I can think of to get examples that &#8220;ought to have the same tag name&#8221;. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In the first demo, I showcase two different versions of a&nbsp;<a href=\"https:\/\/en.wikipedia.org\/wiki\/Material_Design\">Material Design<\/a>&nbsp;button. The next has three slightly different versions of a <a href=\"https:\/\/developer.apple.com\/design\/human-interface-guidelines\/context-menus\">MacOS context menu<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Although the components are copies of existing designs, I wrote most, if not all, of the code surrounding the components since that is the point of articles such as these.&nbsp; You can consider this article an alternative to the <a href=\"https:\/\/developer.chrome.com\/blog\/scoped-registries\">Chrome blog post<\/a> about this feature, mixing in my personal feelings on the feature.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Material Design Button<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If you take away only one thing from this article and forget everything else, I would say it has to be this screenshot from <a href=\"https:\/\/codepen.io\/editor\/chriscoyier\/pen\/019f8a8d-bbdd-7278-a278-1434a9198f37\">the demo Pen<\/a>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"986\" height=\"1024\" src=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/Screenshot-2026-07-30-at-7.39.11-AM.png?resize=986%2C1024&#038;ssl=1\" alt=\"\" class=\"wp-image-10577\" style=\"width:530px;height:auto\" srcset=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/Screenshot-2026-07-30-at-7.39.11-AM.png?resize=986%2C1024&amp;ssl=1 986w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/Screenshot-2026-07-30-at-7.39.11-AM.png?resize=289%2C300&amp;ssl=1 289w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/Screenshot-2026-07-30-at-7.39.11-AM.png?resize=768%2C798&amp;ssl=1 768w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/Screenshot-2026-07-30-at-7.39.11-AM.png?w=1140&amp;ssl=1 1140w\" sizes=\"auto, (max-width: 986px) 100vw, 986px\" \/><figcaption class=\"wp-element-caption\"><em>Different buttons with same tag name and their surrounding HTML structure<\/em><\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">All of those buttons (the dark blue and purple ones, with different border radius, capitalization, etc) are the same element: <code>&lt;md-button&gt;<\/code>.&nbsp;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For those unfamiliar, Material Design was something developed by Google so that different Android applications can have a more cohesive look. The <a href=\"https:\/\/m1.material.io\/components\/buttons.html#buttons-flat-buttons\">button with rounded corners <\/a>is based on the original Material Design 1 specification. The <a href=\"https:\/\/m3.material.io\/components\/buttons\/overview\">button, shaped more like a capsule<\/a>, is based on Material Design 3. Besides Android applications, you\u2019re also likely to see the latter on Google\u2019s Workspace applications in your web browser.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_019f8a8d-bbdd-7278-a278-1434a9198f37\" src=\"\/\/codepen.io\/editor\/anon\/embed\/019f8a8d-bbdd-7278-a278-1434a9198f37?height=650&amp;theme-id=1&amp;slug-hash=019f8a8d-bbdd-7278-a278-1434a9198f37&amp;default-tab=result\" height=\"650\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed 019f8a8d-bbdd-7278-a278-1434a9198f37\" title=\"CodePen Embed 019f8a8d-bbdd-7278-a278-1434a9198f37\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Both buttons were also designed to be themed using the same custom HTML attributes. You can see, for example, that the third button does not have the <code>md-theme=\"md3-violet\"<\/code>, so it defaults to the same color present in the Material Design 1 button.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Context Menu Web Component<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For this example, I\u2019ve decided to take an existing design that comes from Apple\u2019s operating system instead of something related to Google. This next example will feature three similarly named context menus that have similar items yet slightly varying designs.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"576\" src=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/image-1.png?resize=1024%2C576&#038;ssl=1\" alt=\"\" class=\"wp-image-10484\" srcset=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/image-1.png?resize=1024%2C576&amp;ssl=1 1024w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/image-1.png?resize=300%2C169&amp;ssl=1 300w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/image-1.png?resize=768%2C432&amp;ssl=1 768w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/image-1.png?resize=1536%2C864&amp;ssl=1 1536w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/image-1.png?w=1608&amp;ssl=1 1608w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The designs of the context menus were taken from images featured <a href=\"https:\/\/tonsky.me\/blog\/tahoe-icons\/\">in this article<\/a> since I don\u2019t have my own personal MacOS device to extract them myself. It\u2019s not exactly related to this article, but I recommend reading it later too if you haven\u2019t already. It certainly taught me at least a thing or two on what can be considered a human-friendly design.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_019f94de-58cd-711c-a208-00550b175a0f\" src=\"\/\/codepen.io\/editor\/anon\/embed\/019f94de-58cd-711c-a208-00550b175a0f?height=450&amp;theme-id=1&amp;slug-hash=019f94de-58cd-711c-a208-00550b175a0f&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed 019f94de-58cd-711c-a208-00550b175a0f\" title=\"CodePen Embed 019f94de-58cd-711c-a208-00550b175a0f\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">The first two context menus are based on designs from <a href=\"https:\/\/en.wikipedia.org\/wiki\/MacOS_Sequoia\">MacOS Sequioa<\/a> and <a href=\"https:\/\/en.wikipedia.org\/wiki\/MacOS_Tahoe\">MacOS Tahoe<\/a>, respectively, as per the image shown <a href=\"https:\/\/tonsky.me\/blog\/tahoe-icons\/sequoia_tahoe_textedit@2x.webp?t=1782676429\">here<\/a>. The <a href=\"https:\/\/tonsky.me\/blog\/tahoe-icons\/menu_cleanup_color@2x.webp?t=1782676429\">third<\/a> context menu is a personal edit from the author of the blog post that is trying to improve upon the crowded design of Tahoe\u2019s context menu. I know the AI-generated images aren\u2019t exactly one-to-one clones of the images provided by the blog, but most of my attempts have led me to outputs more or less similar to the one shown in the Pen, so here we are.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This example also doesn\u2019t lend itself to any particular theme or background so I\u2019ve decided to use <a href=\"https:\/\/github.com\/ybouane\/liquidglass\">this liquid glass library<\/a> to design the right-click areas of each context menu. Liquid Glass is a design language from Apple that only came out much more recently and was also released alongside macOS Tahoe.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">My Preferred Method of Creating a Scoped Custom Element<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">In this context menu demo, I ended up declaring the scoped elements through <a href=\"https:\/\/developer.chrome.com\/blog\/scoped-registries#scope_to_an_individual_element\"><code>document.createElement<\/code><\/a> on all three of the elements. I believe it would be preferable to avoid methods such as creating a <a href=\"https:\/\/developer.chrome.com\/blog\/scoped-registries#declarative_shadow_dom\">shadow host wrapper<\/a> for every scoped element present in the page. It just usually feels cumbersome to handle styling elements inside the Shadow DOM. In examples such as the ones in the Pen, where the host wrapper only contains the custom element, the Shadow DOM probably isn\u2019t too much of a problem to work around.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The liquid glass library I used, <a href=\"https:\/\/github.com\/ybouane\/liquidglass\">@ybouane\/liquidglass<\/a>, states that glass elements must be direct children of the root. Even if it did get an update that allowed children nested a few elements deep, I doubt authors of small libraries would have Shadow DOM support as a priority. To put it briefly, let\u2019s just say I\u2019m glad this new scoped registries feature doesn\u2019t necessarily require you to create a shadow root in order to be used. It would take me much more work to integrate it in my context menu codepen demo even if it\u2019s not entirely impossible to do so.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Custom Type Declarations<\/h2>\n\n\n\n<p class=\"learn-more wp-block-paragraph\">This part is no longer an issue nowadays if you are using TypeScript 6 (or higher) or simply don\u2019t use TypeScript at all. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I wrote much of the code shown in my demo before TypeScript 6.0 was released, so I may as well share the type declarations I used.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">declare<\/span> global {\n\u00a0 <span class=\"hljs-keyword\">interface<\/span> CustomElementRegistry {\n\u00a0 \u00a0 initialize(root: Node): <span class=\"hljs-built_in\">void<\/span>\n\u00a0 }\n\n\u00a0 <span class=\"hljs-keyword\">interface<\/span> ElementCreationOptions {\n\u00a0 \u00a0 customElementRegistry?: CustomElementRegistry | <span class=\"hljs-literal\">null<\/span> | <span class=\"hljs-literal\">undefined<\/span>\n\u00a0 }\n\n\u00a0 <span class=\"hljs-keyword\">interface<\/span> Document {\n\u00a0 \u00a0 readonly customElementRegistry: CustomElementRegistry | <span class=\"hljs-literal\">null<\/span>\n\u00a0 }\n\u00a0 \u00a0\n\u00a0 <span class=\"hljs-keyword\">interface<\/span> HTMLElement {\n\u00a0 \u00a0 readonly customElementRegistry: CustomElementRegistry | <span class=\"hljs-literal\">null<\/span>\n\u00a0 }\n\n\u00a0 <span class=\"hljs-keyword\">interface<\/span> ShadowRoot {\n\u00a0 \u00a0 readonly customElementRegistry: CustomElementRegistry | <span class=\"hljs-literal\">null<\/span>\n\u00a0 }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/CustomElementRegistry\/initialize\"><code>initialize<\/code><\/a> method is straightforward enough. Simply call it on any DOM Node you query where you want the customElementRegistry to be applied to. Apparently, every Element has this customElementRegistry property now in a browser that supports \u2018Scoped Custom Element Registry\u2019. I\u2019m not sure why the standards committee would prefer this property to be nullable only, rather than also allowing it to be optional or not defined. Perhaps it\u2019s a neat way to let developers check whether the browser supports scoped registries via customElementRegistry on the element?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Another detail I noticed from the specification is that <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Document\/createElement#options\">ElementCreationOptions<\/a> already existed in some browsers before customElementRegistry was made. It also has another property named \u2019is\u2019 which enables you to set the \u2018is\u2019 attribute of an element to an <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Document\/createElement#web_component_example\">existing custom element<\/a>. It\u2019s very unlikely this feature will make it to Baseline, though, as you can see from the\u00a0issue tracker\u00a0listed in the\u00a0MDN page\u00a0referencing this global attribute.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The scoped registry feature, though (unlike the feature I just mentioned that lets you customize built-in elements with <code>is<\/code>) has <a href=\"https:\/\/developer.apple.com\/documentation\/safari-release-notes\/safari-26-release-notes\">Safari support<\/a>. I also think it\u2019s worth mentioning that <a href=\"https:\/\/github.com\/whatwg\/html\/issues\/10854#issuecomment-2816888199\">Safari was also the first<\/a> among the major browsers to implement this standard too.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you ask me, Safari is much more conservative in implementing web standards compared to Chrome while also having a sizable market share unlike Firefox. That\u2019s why I also hold the view that standards ratified first by Safari are more likely to become baseline than those initially pushed by the other two browsers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">While I think these discussions comparing different browsers may be insightful, it\u2019s also <em>really subjective<\/em> and derailing from the main subject of this article. I\u2019ll just end this section reminding readers that the examples later won\u2019t work if you satisfy <a href=\"https:\/\/caniuse.com\/mdn-api_customelementregistry_initialize\">the browser requirements<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Another Creation Method that I Wish Already Existed<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">I wish the light DOM version of the feature could also be done with a declarative syntax, similar to the method using the Declarative Shadow DOM. Something like the following:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"my-host\"<\/span> <span class=\"hljs-attr\">customelementregistry<\/span>&gt;<\/span>\n\u00a0 <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">hello-world<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">hello-world<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n<span class=\"hljs-comment\">&lt;!-- Invoking the following script later down the line...\u00a0 --&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span>&gt;<\/span><span class=\"javascript\">\n\u00a0 <span class=\"hljs-keyword\">const<\/span> registry = <span class=\"hljs-keyword\">new<\/span> CustomElementRegistry()\n\u00a0 registry.define(<span class=\"hljs-string\">\"hello-world\"<\/span>, <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">HTMLElement<\/span> <\/span>{\n\u00a0 \u00a0 connectedCallback() {\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-keyword\">this<\/span>.textContent = <span class=\"hljs-string\">\"Hello World\"<\/span>\n\u00a0 \u00a0\u00a0}\n\u00a0 })\n\n  <span class=\"hljs-keyword\">const<\/span> myHost = <span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">\"my-host\"<\/span>)\n  registry.initialize(myHost)\n<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">As you can see from my example, I would like every HTMLElement to officially support this <code>customelementregistry<\/code> attribute. If you didn\u2019t skip my earlier section talking about type declarations, I also mentioned that every <code>HTMLElement<\/code> now also has its own <code>customElementRegistry<\/code> <em>property<\/em> too.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To give a quick overview of parts from the existing specification that I want to highlight:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The <code>customElementRegistry<\/code> of an element can only be <code>null<\/code> when it is created using JavaScript and is explicitly defined as such.<\/li>\n\n\n\n<li>When <code>customElementRegistry<\/code> is null, the element itself and all its child elements will also inherit the same value. The only exception is cases where an element is already created somewhere else and moved here, in which case it will keep its existing value.<\/li>\n\n\n\n<li><code>registry.initialize(root)<\/code> only works when the element\u2019s <code>customElementRegistry<\/code> property is still null. Running the function on an element where the registry is already initialized does not change the existing registry.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The feature I would like to happen is that when the <code>customelementregistry<\/code> <em>attribute<\/em> is present in an HTMLElement, its <code>customElementRegistry<\/code> <em>property<\/em> would be considered null on its creation. You then call the initialize method of registry to add the registry to the element, similar to the existing approach. So far, this seems straightforward enough, but some keen readers might also want to ask \u201cWhat if I apply this attribute to a custom element itself?\u201d<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-comment\">&lt;!-- Maybe sometimes this direct declaration can be better? --&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">hello-world<\/span> <span class=\"hljs-attr\">customelementregistry<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">hello-world<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">I think this custom element should behave like following:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>That specific custom element tag should remain not defined <strong>even if the tag already exists in the global registry<\/strong>. It needs to be called with the initialize method in order to be properly upgraded to a custom element<\/li>\n\n\n\n<li>If the custom element in question doesn\u2019t exist in the global registry, just treat it similarly to a regular div.<\/li>\n\n\n\n<li>(Optional) If the custom element in question does exist but doesn\u2019t have its own Shadow DOM, use the same behavior stated above. If it does, perhaps make all children of the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTML\/Reference\/Elements\/slot\"><code>slot<\/code><\/a> inherit the registry of the custom element?<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">At the very least, this kind of feature would have been useful in my CodePen demo with liquid glass since it did need the glass elements to be direct children. Though I wouldn\u2019t get my hopes up on such a feature because even if something similar to this gets traction, the Shadow DOM does exist for a reason. I would imagine almost anything added to web standards involving the Shadow DOM implies there would be a big performance tax otherwise. I still think these types of \u201csyntactic sugar\u201d features are at least worth a discussion regardless.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Chrome 146 introduced a scoped custom element registry, allowing different versions of custom elements to share the same tag name without using the global namespace.<\/p>\n","protected":false},"author":40,"featured_media":10537,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[1],"tags":[522,108,3,36],"class_list":["post-10479","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-custom-element-registries","tag-design-systems","tag-javascript","tag-web-components"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/registries.jpg?fit=2000%2C1200&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts\/10479","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/users\/40"}],"replies":[{"embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/comments?post=10479"}],"version-history":[{"count":22,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts\/10479\/revisions"}],"predecessor-version":[{"id":10579,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts\/10479\/revisions\/10579"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/media\/10537"}],"wp:attachment":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/media?parent=10479"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/categories?post=10479"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/tags?post=10479"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}