Make custom elements behave with scoped registries

Jayson Chen
Jayson Chen
Patrick Brosset
Patrick Brosset

Published: March 9, 2026

Custom elements let web app developers build, share, and reuse UI components with their unique behaviors, making development easier. But when your app brings together different sets of custom elements, things can get messy, and name collisions can occur. Scoped custom element registries solve this issue!

The Microsoft Edge team has worked on this feature, and we're excited to announce that scoped custom element registries are now available by default from Edge and Chrome 146, as well as other Chromium-based browsers. You can now encapsulate custom elements, solving a long-standing pain point for developers of components and micro-frontend libraries.

Browser Support

  • Chrome: 146.
  • Edge: 146.
  • Firefox: not supported.
  • Safari: 26.

Source

Scoped custom element registries unlock important patterns for web developers. Now, you can use multiple custom element libraries, developed independently by multiple teams, or multiple versions of the same library side-by-side.

Two buttons with very different styling.

What's a scoped custom element registry?

Today, every custom element definition on a webpage lives in a single, shared registry at window.customElements. This means that if two different libraries both try to define a custom element with the same tag name, like <my-button>, an error will be thrown and the page will break. This is a significant problem in the real world. Large applications that compose their user interfaces from multiple teams, design systems, or micro-frontends can easily run into naming collisions. Scoped custom element registries solve this by letting you create independent registries. Each registry maintains its own set of custom element definitions, completely isolated from the global registry and from each other.

Create a new registry

Instead of defining every custom element in the global window.customElements registry:

  1. Create a new registry by calling new CustomElementRegistry().
  2. Give your new registry a specific scope (see Scoping your registry).
  3. Define the elements that go into the new registry.

Then, when a custom element is used, the browser looks up the element's definition from the associated registry, which isn't necessarily the global one. This means that different parts of your page can use entirely different sets of custom element definitions.

Scope your registry

A custom element registry can be scoped to either a document, a shadow root, or an individual element.

Scope to a shadow root

To scope a new registry to a shadow root, use the customElementRegistry option when calling the attachShadow() method. All the custom elements that are inside that shadow root will now use the definition that's in the corresponding scoped registry:

// Create your custom registry.
const registry = new CustomElementRegistry();
// Define a custom element in the new registry.
registry.define('my-card', class extends HTMLElement {
  connectedCallback() {
    this.textContent = 'Hello from scoped registry!';
  }
});

// Create shadow root, providing it with your new custom element registry.
const host = document.querySelector('#host');
const shadow = host.attachShadow({
  mode: 'open',
  customElementRegistry: registry,
});

shadow.innerHTML = '<my-card></my-card>';

Declarative shadow DOM

Use the shadowrootcustomelementregistry attribute on a <template> element to tell the browser that the resulting shadow root is using a scoped registry rather than the global one:

<my-host>
  <template shadowrootmode="open" shadowrootcustomelementregistry>
    <my-widget></my-widget>
  </template>
</my-host>
  const registry = new CustomElementRegistry();
  registry.define('my-widget', class extends HTMLElement {
    connectedCallback() { this.textContent = 'Scoped!'; }
  });

  const shadow = document.querySelector('my-host').shadowRoot;
  registry.initialize(shadow);

As shown in the example, the attribute reserves space for the custom element registry, and the user needs to define and initialize the registry to the shadow root Scoping to a disconnected document

You can also scope a registry to a document, such as one created by document.implementation.createHTMLDocument(). This lets you clone <template> elements, and do off-screen document manipulation, each with their own isolated sets of component definitions. To do this, use the CustomElementRegistry.initialize() method:

// Create a new registry.
const registry = new CustomElementRegistry();
registry.define('app-widget', AppWidget);

// Create a document, and use registry.initialize() to scope the registry to the document.
const doc = document.implementation.createHTMLDocument('');
registry.initialize(doc);

doc.body.innerHTML = '<app-widget></app-widget>';

Scope to an individual element

You can also associate a registry directly with an element and its subtree by passing the customElementRegistry option to document.createElement(). The element and its descendants will resolve against that registry, no matter which part of the DOM they are later inserted into:

// Create a registry and define a custom element in it.
const registry = new CustomElementRegistry();
registry.define('fancy-label', FancyLabel);

// Create a new DOM element and scope the new registry to it.
const el = document.createElement('fancy-input', {
  customElementRegistry: registry,
});

// Use a custom element in the new DOM element.
el.innerHTML = '<fancy-label>Name</fancy-label>';

Learn more

To learn more, check out the Edge demo and its source code, and read MDN's CustomElementRegistry reference article

Scoped custom element registries are now enabled by default in both Microsoft Edge and Chrome, as well as in other Chromium-based browsers, thanks to our collaboration through the Chromium project.

You don't need to enable any setting or sign-up to an origin trial. You can just start using the feature today in Chromium-based browsers.

If you need this feature to be implemented in other browsers, give the Scoped custom element registries issue a thumbs-up reaction, and leave a comment with your use cases and workarounds.

If you find a bug with the implementation of the feature in a Chromium-based browser, open an issue for us to investigate at crbug.com/new.

We hope that scoped custom element registries make using web components easier.