How can I restrict styling a closed Shadow DOM to particular outer hosts?

132 Views Asked by At

I'd like to style an inner custom element's closed Shadow DOM from an outer custom element. – Outside the outer custom element, the inner Shadow DOM should not be stylable.

So, in general, a particular parent element is supposed to style its children, but outside the parent element, nothing should be able to style these children.

How would I do that?

AFAIK, the part attribute cannot be restricted to particular hosts or a maximum nesting level.

Example

Imagine a custom element night-sky that's supposed to contain hundreds of star-item custom elements:

<night-sky>
  <star-item/>
  <star-item/>
  <star-item/>
  <star-item/>
  ...
</night-sky>

The <night-sky> custom element's definition is supposed to set each <star-item> custom element's common CSS rules, e.g.: position: absolute; width: 8px; height: 8px; background-image: url(star.png);

I don't want to apply these common CSS rules to each single <star-item> custom element itself as it would add an undesired burdon to the browser. So, I want the <night-sky> custom element to set the common CSS rules once for all its children. However, outside the <night-sky> custom element I don't want anything to be able to alter the <star-item> custom elements' style.

1

There are 1 best solutions below

7
Danny '365CSI' Engelman On

Reverse your logic.

Make <star-item> read its common styles from a definition inside <night-sky>

But you can't block inheritable styles
Only block Specificity with !important (or add more wrapping Element Nodes and styles)

A closed shadowRoot is only about script access through (no longer available) this.shadowRoot

Minor note: ::slotted targets Element Nodes, not Text Nodes (see FOO)

customElements.define("star-item",class extends HTMLElement{
  constructor(){
    super();
    (this.root = this.attachShadow({mode:"closed"}))
     .innerHTML = `<style>
                     :host { color: beige!important }
                   </style>
                   <style id="common"></style>
                   <b part="title">Web Component</b> <slot></slot><br>`;
  }
  connectedCallback(){
    let sky = this.closest("night-sky");
    let html = sky.querySelector("template[id='star-item']").innerHTML;
    this.root.querySelector("style[id='common']").innerHTML = html;
  }
})
star-item{ font:21px arial;background:lightcoral;color:white} /* inheritable styles */

star-item::part(title){ /* parts do style closed shadowRoots */
  font-style:italic;
}
<night-sky>
  <template id="star-item">
      :host(.active){ background:lightgreen!important; color:black!important } 
      ::slotted(*){ background:green;color:gold }
  </template>
  <star-item>FOO</star-item>
  <star-item><span>BAR</span></star-item>
  <star-item class="active"><b>BAZ</b></star-item>
</night-sky>