2

I have created a vanilla web component or HTML element. It just displays two links.

To encapsulate the thing, I use shadow DOM. However it does not seem to be encapsulated. In the DOM tree it's inside #shadow-root which is good.

Why does the web component use the global style instead of the style I provided in the template for my web component?

The text is red and I expected it to be green.

class MyEl extends HTMLElement {
  constructor() {
    super();
    this.shadow = this.attachShadow({ mode: "open" });
  }

  connectedCallback() {
    const template = `
      <style>
        a {
          color: green;
        }
      </style>
      <slot></slot>`;
    this.shadow.innerHTML = template;
  }
}

window.customElements.define("my-el", MyEl);
a {
  color: red
}
  <my-el>
    <a href="example.com">Item1</a>
    <a href="example.com">Item2</a>
  </my-el>
Jens Törnell
  • 23,180
  • 45
  • 124
  • 206

3 Answers3

1

The full, detailed explanation is in: ::slotted CSS selector for nested children in shadowDOM slot

TL;DR

Your links are in lightDOM and thus styled by its DOM (in your code the document DOM)
Moving the nodes from lightDOM to shadowDOM is one "solution"; but you are not using slots then.

FYI, your code can be compacted to:

class MyEl extends HTMLElement {
  constructor() {
    super().attachShadow({ mode: "open" })
           .innerHTML = `<style>a{color:green}</style><slot></slot>`;

  }
}

window.customElements.define("my-el", MyEl);

More SLOT related answers can be found with StackOverflow Search: Custom Elements SLOTs

Danny '365CSI' Engelman
  • 16,526
  • 2
  • 32
  • 49
1

While this question already has an accepted answer, moving a slot's children to the shadowRoot isn't desirable for most use cases.

What you probably want to do is to use the ::slotted() selector.

Just bear in mind that styles applied to a slot's children through the ::slotted() selector only act as "default" styles and can still be overridden by using styles in light DOM.

For example, check this edited version of your snippet:

As you can see, this time my-el tries to apply both a color and a text-decoration style to anchor (<a>) children in any of it's slots.

However, in light dom, we have a a.special selector that overrides the color, so the <a class="special"> will be red, not green

class MyEl extends HTMLElement {
  constructor() {
    super();
    this.shadow = this.attachShadow({ mode: "open" });
  }

  connectedCallback() {
    const template = `
      <style>
        ::slotted(a) {
          color: green;
          text-decoration: none;
        }
      </style>
      <slot></slot>`;
    this.shadow.innerHTML = template;
  }
}

window.customElements.define("my-el", MyEl);
a.special {
  color: red
}
  <my-el>
    <a href="example.com">Item1</a>
    <a class="special" href="example.com">Item2</a>
  </my-el>
Alan Dávalos
  • 2,568
  • 11
  • 19
0

observe this line, you have to move/copy elements to shadow for example with:

this.shadow.innerHTML = this.innerHTML + template;

I've added this to demonstrate that only inline style will be applied to shadow dom elements .. so copied links in SD are using your style :)

so red will be GLOBAL, green will be SHADOW elements

enter image description here

class MyEl extends HTMLElement {
  constructor() {
    super();
  }

  connectedCallback() {
    this.shadow = this.attachShadow({ mode: "open" });
    const template = `
      <style>
        a {
          color: green;
        }
      </style>
      <slot></slot>`;
    this.shadow.innerHTML = this.innerHTML + template;
  }
}

window.customElements.define("my-el", MyEl);
a {
  color: red
}
<my-el>
    <a href="example.com">Item1</a>
    <a href="example.com">Item2</a>
  </my-el>
Kresimir Pendic
  • 3,597
  • 1
  • 21
  • 28