How to insert an element into a web component's slot via JavaSCript

Viewed 22

In the below examples, foo is the parent component, and bar is the child that fits into foo's slot.

As you can see below I can slot an element if I place it in the HTML directly, but when I try slot an element via JavaScript, it is not slotted, and is not styled by the ::slotted(*) selector. How does one append an element to a web component's slot?

class ComponentFoo extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: "open" });
        this.shadowRoot.appendChild(TEMPLATE_FOO.content.cloneNode(true));

    }
    connectedCallback() {
        const slotEl = this.shadowRoot.getElementById("SLOT");
        const bar = document.createElement("wc-bar");
        bar.textContent = "Bar created via Javascript"
        slotEl.appendChild(bar)  // <--- Does not work
        console.log(bar)
    }
};
window.customElements.define("wc-foo", ComponentFoo);

class ComponentBar extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: "open" });
        this.shadowRoot.appendChild(TEMPLATE_BAR.content.cloneNode(true));
    }
};
window.customElements.define("wc-bar", ComponentBar);
<template id="TEMPLATE_FOO">
  <div>foo</div>
  <slot id="SLOT"></slot>
  <style>
    ::slotted(*) {
        color: red;
        border: 1px solid lime;
    }
  </style>
</template>

<template id="TEMPLATE_BAR">
  <div>bar</div>
</template>

<wc-foo>
    <div>bar created via HTML</div>
</wc-foo>

1 Answers

Because slotted content is NOT moved to a <slot>!

slotted content is REFLECTED into the slot from its container lightDOM

So if you assign content to a <slot> it becomes its default content, shown only when there is nothing reflected in the <slot> (thus :slotted won't style it, because there is nothing slotted)

For :slotted deep dive, see: ::slotted CSS selector for nested children in shadowDOM slot

class MyBaseClass extends HTMLElement{
  constructor() {
      super()
       .attachShadow({mode:"open" })
       .append(
         document.getElementById(this.nodeName).content.cloneNode(true),
         Object.assign(document.createElement("style"),
         {
          innerHTML : `:host{ display:block;margin:5px }`       
         })
       );
  }
}
customElements.define("wc-foo", class extends MyBaseClass {
  connectedCallback() {
      const bar = document.createElement("wc-bar");
      bar.innerHTML = "Bar created via Javascript";
      this.append(bar); // TO: lightDOM!!!
      this.shadowRoot
          .querySelector("slot[name='unused']")
          .append("Hello default content in an empty slot");
  }
});

customElements.define("wc-bar", class extends MyBaseClass {});
<template id="WC-FOO">
  <div>div in WC-FOO</div>
  <slot></slot>
  <slot name="unused"></slot>
  <style>
    ::slotted(*) {
        background: pink;
    }
  </style>
</template>

<template id="WC-BAR">
  <style>* { background: lightgreen } </style>
  <div>div in WC-BAR</div>
  <slot></slot>
</template>

<wc-foo>
    <div>Line Foo 1</div>
    <wc-bar>Line Foo 2</wc-bar>
</wc-foo>

Related