2

I have a web component that should accept an arbitrary element to wrap its content in. Although I can see in Chrome Dev Tools that the slots are properly assigned, nothing appears in the DOM. Has anybody seen this issue before?

Definition

class ExampleParent extends HTMLElement {
  constructor() {
    super();
    const shadowRoot = this.attachShadow({ mode: 'open' });
    shadowRoot.innerHTML = `
      <slot name="as">
        <slot name="prefix"></slot>
        <slot></slot>
      </slot>
    `;
  }
}
customElements.define('example-parent', ExampleParent);

Invocation

<example-parent
  style="
    min-width: 100px;
    height: 40px;
    border: 1px solid red;
    display: inline-block;
  "
>
  <button slot="as"></button>
  <div slot="prefix">Prefix slot</div>
  Default
</example-parent>

Actual result

enter image description here

Expected result

enter image description here

Source code

https://stackblitz.com/edit/nested-slots-ahtfyf?file=index.html

drewloomer
  • 93
  • 2
  • 4

2 Answers2

2

You can't have nested <slot> elements kinda like you can't have nested <li> elements.

So like with <li> where you add an extra <ul> container,
you have to add an extra Web Component which passes on slot content:

update: added exportparts and part to show how global CSS styles nested shadowDOMs JSWC

class MyCoolBaseClass extends HTMLElement {
  constructor( html ) {
    let style = "<style>:host{display:inline-block;background:pink}</style>";
    super().attachShadow({mode:'open'}).innerHTML = style + html;
  }
}
customElements.define('el-one', class extends MyCoolBaseClass {
  constructor() {
    super(`<el-two exportparts="title"><slot name="ONE" slot="TWO"></slot></el-two>`);
  }
});
customElements.define('el-two', class extends MyCoolBaseClass {
  constructor() {
    super(`Hello <slot name="TWO"></slot> <b part="title">Web Components</b>`);
  }
});
el-one { display:block; font-size:21px }
el-one::part(title){  color:green  }
<el-one><b slot="ONE">Fantastic!</b></el-one>
<el-one><b slot="ONE">Great!</b></el-one>
Danny '365CSI' Engelman
  • 16,526
  • 2
  • 32
  • 49
  • This is great! I ended up stumbling upon something similar but your answer makes clear why it works. I ended up abandoning this approach because, while this works, styles set on the base class can no longer affect the contents stuffed inside the intermediary component. – drewloomer Sep 15 '22 at 20:05
  • See CSS [::part](https://developer.mozilla.org/en-US/docs/Web/CSS/::part) – Danny '365CSI' Engelman Sep 16 '22 at 06:45
  • I added ``exportparts`` and ``part`` to the example, so show how to '_export_' nested shadowDOM content for **global CSS** styling – Danny '365CSI' Engelman Sep 16 '22 at 07:00
1

I can see what you're trying to do, which is that you want the shadow DOM to contain

<button>
  <div>Prefix</div>
  Default
</button>

but unfortunately that's not possible via <slot> elements.

Per the HTML spec

A slot element represents its assigned nodes, if any, and its contents otherwise.

This means that the entire <slot name="as"> element and its contents are replaced by the <button slot="as"> element, and the contents of the <slot> are discarded.

The reason for this is that the browser has no way of knowing how to add descendants to the provided replacement, as a void element (such as <img>), or deeply nested structure could have been provided for the slot replacement.

zzzzBov
  • 174,988
  • 54
  • 320
  • 367