In the below example, I am trying to style the **host**
depending on whether it has a slotted element that has the empty
attribute. If it does have such an element, then I wish to add a lime green border:
class Component extends HTMLElement {
constructor() {
super().attachShadow({mode:'open'});
const template = document.getElementById("TEMPLATE"); this.shadowRoot.appendChild(template.content.cloneNode(true));
}
}
window.customElements.define('wc-foo', Component);
<template id="TEMPLATE">
<style>
:host(:has(::slotted([empty]))) {
border: 2px solid lime;
}
</style>
<div>Custom web component should have a lime border
</div>
<slot></slot>
</template>
<wc-foo>
<div empty>"Empty Div"</div>
</wc-foo>
However this does not work, and I am not sure why. Guessing probably because the :host()
selector has to be a simple selector. Is there any other way of achieving it?
PS: This question is not a dup of How to use ":host" (or ":host()") with ":has()" cause that is about selecting the host's children, whereas I am trying to select the host depending on its children.
As of writing this answer,
:host(:has(...))
selecting the light DOM is only implemented in Safari.So the following example currently only works in Safari:
While researching this question, I found some GitHub issues that may provide more context: https://github.com/web-platform-tests/interop/issues/208 . This Safari only answer is also courtesy of Westbrook's GitHub comment: https://github.com/w3c/webcomponents-cg/issues/5#issuecomment-1220786480
What is a solution that works in all browsers today?
You can reflect an attribute onto the host to style the host. To detect that your element has been slotted by the element containing the
empty
attribute, you can use theslotchange
event. In the slotchange event reflect a styling attribute onto the host, and use the selector::host([empty])
to add the lime border.