0

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>
run_the_race
  • 1,344
  • 2
  • 36
  • 62

1 Answers1

1

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>
Danny '365CSI' Engelman
  • 16,526
  • 2
  • 32
  • 49
  • Thank you! I didn't click that you actually append to the host, and reading MDN I didnt come across that point too, so I never would have figured that out thank you! In your experience, do you prefer to slot childen to components that have two parts, a `parent` and `child`, and the parent only ever has children of that `child` type. Or do you find slots not so beneficial and just append the children it to some element and keep it all in the shadow DOM? Maybe the answer is it depends, just trying to get a feeling if I should try use `slots` more often, and they are good practice or not. – run_the_race Sep 12 '22 at 09:27