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>
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
const createElement = (t,p={}) => Object.assign(document.createElement(t),p);
class MyBaseClass extends HTMLElement{
constructor() {
super()
.attachShadow({mode:"open" })
.append(
document.getElementById(this.nodeName).content.cloneNode(true),
createElement("style", {
innerHTML: `:host{ display:block;margin:5px }`
})
);
}
}
customElements.define("wc-foo", class extends MyBaseClass {
connectedCallback() {
this.append(// TO: lightDOM!!!
createElement("wc-bar", {
innerHTML: "Bar created via Javascript"
})
);
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>