Pass Data to a Custom Element

Custom elements are the building blocks of third-party web components. Pass data to a custom element using an attribute, property, or the lwc:spread directive. When passing data, LWC sets the data as attributes by default, and sets properties only if they exist.

The constructor that initializes your shadow content using this.attachShadow({ mode: 'closed' }) is only invoked once. Consider these guidelines when working with an attribute or property.

Pass Data Using An Attribute 

After a third-party web component is rendered, attribute changes are ignored. To observe attributes and ensure the third-party web component renders your changes, use the observedAttributes() static getter and attributeChangedCallback() method. The attributeChangedCallback() callback runs when an observed attribute is changed. To handle serializing and deserializing of the attribute data, use a getter and setter such as in this example.

1class extends HTMLElement {
2    static observedAttributes = ["myAttr"];
3    attributeChangedCallback(attr, oldVal, newVal) {
4      if (attrName === "myAttr") {
5        this.shadow.getElementById("myElement").myAttr = newVal === "true";
6      }
7    }
8    set myAttr(bool) {
9      this.setAttribute("myAttr", bool.toString());
10    }
11    get myAttr() {
12      return this.getAttribute("myAttr") === "true";
13    }
14}

See an example that increments a button with an attribute change.

Pass Data Using Properties 

To work with properties, use a getter and setter.

1class extends HTMLElement {
2  _message = 'Hello';
3  set message(value) {
4    this._message = value;
5  }
6  get message() {
7    return this._message;
8  }
9}

For example, you have a custom element with a property that’s passed to the custom element using the lwc:spread directive.

1<!-- myMessage.html -->
2<template>
3  <c-message lwc:external lwc:spread={props}></c-message>
4</template>

The custom element displays a button. To add an event listener to the button, use addEventListener().

1// myMessage.js
2import { LightningElement } from "lwc";
3
4customElements.define(
5  "c-message",
6  class extends HTMLElement {
7    constructor() {
8      super();
9      this.shadow = this.attachShadow({ mode: "closed" });
10      this.shadow.innerHTML = `<button>click</button>`;
11      this.shadow.querySelector("button").addEventListener("click", (event) => {
12        console.log(`message: ${this.message}`);
13      });
14    }
15
16    set message(value) {
17      this._message = value;
18    }
19    get message() {
20      return this._message;
21    }
22  },
23);
24
25export default class MyMessage extends LightningElement {
26  props = {
27    message: "Hello custom element",
28  };
29}

Pass Data to a Child Component 

Consider a parent component that contains a child component that defines a custom element. Pass the properties to a child component using the lwc:spread directive.

1<!-- myApp.html -->
2<template>
3  <c-cmp lwc:spread={myProps}></c-cmp>
4</template>

Use an object with key-value pairs.

1// myApp.js
2import { LightningElement } from "lwc";
3
4export default class MyApp extends LightningElement {
5  myProps = {
6    name: "Guest",
7    greeting: "Hello",
8  };
9}

In your child component, create an instance of the custom element.

1<!-- myCmp.html -->
2<template>
3  <c-custom-el lwc:external> {greeting}, {name} </c-custom-el>
4</template>

Call the constructor() in your JavaScript and expose your properties to the parent component.

1// myCmp.js
2import { LightningElement, api } from "lwc";
3
4customElements.define(
5  "c-custom-el",
6  class extends HTMLElement {
7    constructor() {
8      super();
9      this.attachShadow({ mode: "closed" }).innerHTML = "<slot></slot>";
10    }
11  },
12);
13export default class MyCmp extends LightningElement {
14  @api name;
15  @api greeting;
16}

The custom element renders like this.

1<my-app>
2  #shadow-root (open)
3  |  <my-cmp> 
4  |    #shadow-root (open) 
5  |    |  <c-custom-el>
6  |    |    #shadow-root (closed)
7  |    |    | Hello, Guest
8  |    |  </c-custom-el>
9  |  </my-cmp>
10</my-app>

Pass Markup to a Slot in the Custom Element 

Passing markup to a slot in a third-party web component behaves similarly to a slot in an LWC component.

Slotting implementation for synthetic shadow is not supported in third-party web components.

Note

Consider a third-party web component with some markup.

1customElements.define(
2  "c-custom-slot",
3  class extends HTMLElement {
4    constructor() {
5      super();
6      this.attachShadow({ mode: "closed" }).innerHTML = `
7    <h1>My title</h1>
8    <div>
9        <p>Some content here</p>
10    </div>
11    <slot></slot>
12    `;
13    }
14  },
15);

The following slotted content appears in the <slot> element.

1<template>
2  <c-custom-slot lwc:external>
3    <div class="slotted">slot content</div>
4  </c-custom-slot>
5</template>

The component renders in the DOM like this.

1<c-custom-slot>
2  #shadow-root (closed)
3  |  <h1>My title</h1>
4  |  <div><p>Some content here</p></div>
5  |  <slot>
6  |    <div class="slotted">slot content</div>
7  |  </slot>
8</c-custom-slot>

Similarly, you can use a named slot like this.

1// mySlot.js
2import { LightningElement, api } from "lwc";
3
4customElements.define(
5  "c-slotting",
6  class extends HTMLElement {
7    constructor() {
8      super();
9      this.shadow = this.attachShadow({ mode: "closed" });
10      this.shadow.innerHTML = `<slot name="myslot"></slot>`;
11    }
12  },
13);
14export default class MySlot extends LightningElement {}

Include the named slot in your markup.

1<!-- mySlot.html -->
2<c-slotting lwc:external>
3  <p>slotted incorrectly</p>
4  <p slot="myslot">slotted correctly</p>
5</c-slotting>

The component renders in the DOM like this.

1<my-slot>
2  #shadow-root (open)
3  |  <c-slotting>
4  |    #shadow-root (closed)
5  |    |  <p>slotted incorrectly</p>
6  |    |  <p slot="myslot">slotted correctly</p>
7  |  </c-slotting>
8</my-slot>

In native shadow components, slotted content is preserved in top-level CustomElementConstructor elements.

For example, this top-level <slot> element in an index.html file includes slotted content.

1<c-custom-slot>
2  <div class="slotted">Pre-existing slot content</div>
3</c-custom-slot>

The component renders in the DOM like this. It retains the original slotted content.

1<c-custom-slot>
2  #shadow-root (closed)
3  |  <slot>
4  |    <div class="slotted">Pre-existing slot content</div>
5  |  </slot>
6</c-custom-slot>

Work with Events in Third-Party Web Components 

Events in third-party web components behave similarly with events in LWC. The event bindings support only lowercase events. To use events with non-lowercase names, add an event listener using the addEventListener() API.

Add your event listeners in the constructor().

1customElements.define(
2  "c-element-with-events",
3  class extends HTMLElement {
4    constructor() {
5      super();
6      this.attachShadow({ mode: "closed" });
7      this.shadow.innerHTML = `CLICK ME!`;
8      this.addEventListener("click", this.handleClick);
9    }
10    handleClick() {
11      this.dispatchEvent(new CustomEvent("lowercaseevent"));
12      this.dispatchEvent(new CustomEvent("camelEvent"));
13    }
14  },
15);

Third-Party Web Component Considerations 

A custom element that isn’t registered renders as an instance of the native HTMLUnknownElement interface, which extends HTMLElement without adding any properties or methods. The browser then treats the external component as a native component no different from a span or a div.

For registered components, the engine renders the associated third-party web component and defers the upgrading to the browser.

For more information, see HTML Spec: Upgrading elements after their creation.

Additionally, consider these upgrade behaviors on third-party web components.

  • If a third-party web component is not upgraded, LWC sets its attributes on mount and on update.
  • If there’s a delayed upgrade, the attribute is set instead of the property.
  • After the upgrade, the property is set instead of the attribute, if the property exists.

See Also

Release Preview

This release is in preview. Features described here don't become generally available until the latest general availability date that Salesforce announces for this release. Before then, and where features are noted as beta, pilot, or developer preview, we can't guarantee general availability within any particular time frame or at all. Make your purchase decisions only on the basis of generally available products and features.