Work with Custom Elements (Beta)

This feature is a Beta Service. Customer may opt to try such Beta Service in its sole discretion. Any use of the Beta Service is subject to the applicable Beta Services Terms provided at Agreements and Terms.

Third-party web components are web components that you can create using customElements.define(), resulting in custom elements you can reuse in an LWC app. You don't need to use custom elements with LWC unless you're working with third-party web components. When you implement a third-party web component in LWC, we recommend that you refer to that component's documentation for usage information. For the purpose of this article, custom elements and third-party web components are interchangeable.

Lightning Web Security (LWS) must be enabled in the Salesforce org because Lightning Locker doesn’t support third-party web components.

A custom element must follow these characteristics.

  • Define a component class that extends HTMLElement.
  • Register the custom element within the CustomElementRegistry using customElements.define(name, constructor). The name must contain a hyphen and be unique on a page.

Using a <template> tag in your custom element is optional. You can create a <template> tag using document.createElement("template");. You can't use a nested <template> tag in your LWC HTML template.

To keep your custom element separate and maintainable, you can define and register the custom element in several ways.

  • In the LWC JavaScript file before the LWC component class definition
  • In a separate JavaScript file within the LWC bundle

Third-party web components contain a custom element definition, which extends the HTMLElement class. The custom element definition describes how to show the element and what to do when the element is added or removed.

The HTML specification states that the constructor is used to set up initial state, default values, event listeners, and a shadow root.

The third-party web component behavior is described in lifecycle callbacks, such as in connectedCallback() or disconnectedCallback().

  • constructor()-Called when the custom element is initialized. To establish the prototype chain, the constructor must call super() first and can specify any pre-rendering processes like setting the content of the shadow. The constructor sets up the initial state and default values, and registers event listeners. The constructor also attaches a shadow root to the custom element using this.attachShadow().
  • connectedCallback()-Invoked when the custom element is connected to the DOM.
  • disconnectedCallback()-Invoked when the custom element is disconnected from the DOM.
  • observedAttributes()-Returns an array of attributes to observe.
  • attributeChangedCallback()-Invoked when an attribute is added, removed, or changed. Specify the attributes to observe in observeAttributes().
  • adoptedCallback()-Invoked when the custom element is moved to a new document.

A custom element attaches a shadow root to its internals using the attachShadow() Web API method, which specifies one of these encapsulation modes-open or closed. Using open mode, third-party custom elements can use appendChild to append HTML tags like <div> and <style> to its shadow root. They can also use adoptedStyleSheets and other Web APIs on the shadow root.

The custom element renders like this in the DOM.

In closed mode, JavaScript access to the shadow DOM tree is blocked and shadowRoot returns null. A custom element can append HTML tags using the ShadowRoot.innerHTML property.

To create a custom element in closed mode, save the reference to the shadow root with a different variable, such as shadow or __shadow.

Despite its name, closed mode doesn't completely block access to a custom element's shadow root. For example, a custom element can query a tag in a closed shadow root and update its internals.

The custom element renders like this in the DOM.

For more information, see MDN Web Docs: Element.shadowRoot and the "mode" option.

To create the internal shadow DOM structure, a custom element can append content using .innerHTML or DOM APIs such as createElement() and appendChild(). This example defines the custom element outside of the LWC component class.

To work with the custom element's attributes and children, use connectedCallback() or renderedCallback() instead. For example, if you're creating elements and setting attributes on them, defer them to one of the lifecycle callbacks and use attributeChangedCallback() to define a callback when the attribute is changed. For more information, see the custom element specification.

Use the custom element in your LWC template with the lwc:external directive.

If you define and register a custom element in a separate JavaScript file within a component bundle, import the JavaScript file in your LWC JavaScript file. For example, define and register the custom element in a JavaScript file myCustomElement.js in the myComponent component bundle that follows this folder structure.

In the myCustomElement.js file, define and register the custom element.

To import the JavaScript file from your Lightning web component, use the import syntax.

In the HTML template file, create an instance of your custom element using lwc:external.

Here's an example to demonstrate the structure of a custom element. The example creates a button that increments the counter on its label when pressed.

To use the custom element in LWC, add it to your template using the lwc:external directive.

The component renders in the DOM like this. The button label ${this.count} is incremented each time it's clicked.

This example is similar to the previous example, but it watches the custom element's count attribute, using attributeChangedCallback() to define a callback when the attribute is changed.

Add the custom element tag to your LWC template.

This example is adapted from the MDN lifecycle callbacks article. It uses a <custom-square> custom element that's defined and registered in a separate customSquare.js JavaScript file within the same myCustomSquare component bundle.

The customSquare.js file creates a class for the custom element and registers it using customElements.define("custom-square", Square);. It also exports the random function, so that the myCustomSquare component can import and use it.

The myCustomSquare.js file contains the button click handlers to add, update, and remove the custom square from the DOM. The JavaScript initializes the custom attributes and updates these attributes on the custom element. It also imports the random function from the customSquare.js file that defines and registers the custom element.

We don’t recommend using JavaScript to manipulate the DOM. It's better to use HTML directives to write declarative code. This example uses lwc:if to conditionally display the custom element instead of using appendChild and removeChild. Using the conditional directive also triggers the disconnectedCallback and connectedCallback lifecycle callbacks for the custom element.

Add the buttons to the myCustomSquare HTML template file.

See Also