cssweb-component

Prioritizing slotted styling over global styling


Let's say I have an app where paragraphs are red by default, but I want to use a custom element (let's call it <blue-stuff>) to style certain paragraphs as blue and bold. In the snippet below, I attempt to do this with a web component and shadow dom below, relying on the ::slotted pseudo-element:

customElements.define('blue-stuff', class extends HTMLElement {
  constructor() {
    super()
      .attachShadow({ mode: 'open' })
      .appendChild(document.importNode(document.getElementById('blue-template').content, true))
  }
})
p {
  color: red;
}
<template id="blue-template">
  <style>
    .blue ::slotted(p) {
      color: blue;
      font-weight: bold;
    }
  </style>
  
  <div class="blue">
    <slot></slot>
  </div>
</template>

<p>Hello I am red!</p>
<blue-stuff>
  <p>Hello, I am supposed to be blue and bold!</p>
</blue-stuff>

What surprises me is the paragraph that's supposed to be blue is in fact red, meaning it's prioritizing the simple p selector over the .blue ::slotted(p) selector. Normally, specificity would solve this problem but it appears in this case, styling in the "light" dom gets preferred over the shadow dom.

The question: In my example, is it possible for the paragraph in <blue-stuff> to be styled as blue without the use of !important?

I've thought of so far:

I've worked on this in Firefox.


Solution

  • You are falling in the <slot> trap

    For long answer see: ::slotted CSS selector for nested children in shadowDOM slot

    customElements.define('blue-stuff', class extends HTMLElement {
      constructor() {
        super()
          .attachShadow({mode: 'open'})
          .append(
                  document.getElementById(this.nodeName)
                          .content
                          .cloneNode(true)
                 );
        this.onclick = () => BLUE.disabled = !BLUE.disabled;
      }
    })
    p {
      color: red;
      font-size: 20px;
      font-family: Arial;
    }
    span {
      background:gold;
    }
    <template id="BLUE-STUFF">
      <style>
       ::slotted(p) {
          color      : blue;  /* not applied because inheritable color:red */
          font-weight: bold;  /* applied, no inherited setting */
          cursor: pointer;    /* applied, no inherited setting */
          font-family: Georgia !important; /* be a crap Designer, force it */
        }
        ::slotted(span){
            background:black !important; 
            /* won't work, ::slotted can only style lightDOM 'skin' (p) */
        }
      </style>
      <slot></slot>
    </template>
    <style id=BLUE onload="this.disabled=true">/* click to toggle stylesheet */
      blue-stuff p{
        color:blue;  
        font-weight: normal;
        font-family: Arial !important; /* not applied, fire that 'designer'! */
      }
    </style>
    <blue-stuff>
      <p>I <span>must</span> be styled by my container DOM [click me]</p>
    </blue-stuff>

    blue-stuff p {
      color:blue
    }