v146 · Web Components · Custom Elements
Scoped Custom Element Registry
Chrome 146 ships Scoped Custom Element Registries — a way to create isolated CustomElementRegistry instances and associate them with shadow roots. This allows the same custom element tag name (e.g. <my-button>) to be defined differently in two independent registries, eliminating naming conflicts when composing web apps from third-party component libraries.
concepts
-
Registry Isolation
Two shadow roots each get their own
CustomElementRegistry. Both define<x-widget>with different implementations. The page global registry has no<x-widget>at all — each shadow root renders its own version without conflict. -
Shadow DOM Scoping
Step-by-step walkthrough of how scoped registries attach to shadow roots, how elements in the shadow DOM resolve tag names against the scope's registry, and how the global registry remains unaffected.
-
Nested shadow isolation
Two "third-party widgets" each ship a
<widget-button>with conflicting implementations. Flip between scoped and legacy global modes and watch the second widget either render correctly or crash with a name collision. -
Version Coexistence
v1 and v2 of a
<ds-button>design system component run on the same page — same tag name, different visual styles and API — each in its own scoped registry. Click "Simulate global collision" to see theNotSupportedErroryou'd get without scoping.
why it shipped
The global custom element registry (window.customElements) enforces unique tag names across an entire page. If two component libraries both try to define <my-button>, one will silently fail or throw. Large-scale applications composed from multiple independent design systems hit this problem at scale. Scoped Custom Element Registries solve it: each shadow root can have its own registry — you attach a new CustomElementRegistry() to a shadow root and define elements into it. Those definitions are invisible to the global registry and to other scoped registries, making name collisions structurally impossible.
the API
// Create a new isolated registry
const registry = new CustomElementRegistry();
// Attach it to a shadow root
const host = document.createElement('div');
const shadow = host.attachShadow({ mode: 'open', registry });
// Define an element in THIS registry only
registry.define('x-button', class extends HTMLElement {
connectedCallback() {
this.textContent = 'Library A button';
}
});
// A second host with a different registry — same tag name, different impl
const registry2 = new CustomElementRegistry();
const host2 = document.createElement('div');
const shadow2 = host2.attachShadow({ mode: 'open', registry: registry2 });
registry2.define('x-button', class extends HTMLElement {
connectedCallback() {
this.textContent = 'Library B button';
}
});
// Insert <x-button> in each shadow — each resolves to its own definition
shadow.innerHTML = '<x-button></x-button>'; // → "Library A button"
shadow2.innerHTML = '<x-button></x-button>'; // → "Library B button"
// Global registry is unaffected — no x-button defined there
customElements.get('x-button'); // → undefined