'Web Components - Using <slot> to fill in text with no tags

Consider the following HTML and JS

customElements.define("my-comp", class extends HTMLElement {
  constructor() {
    super();
    let template = document.getElementById('my-template');
    let templateContent = template.content;

    const shadowRoot = this.attachShadow({
      mode: 'open'
    });
    shadowRoot.appendChild(templateContent.cloneNode(true));
  }
})
<html>
  <style>
    span {
      border: 1px solid red
    }
  </style>
  
  <template id="my-template">
        I'm a new custom component using slots with value <slot name="slot1">default</slot>
  </template>

  <my-comp>
    <span slot="slot1">slotted value</span>
  </my-comp>
  
  <p> </p>
  
  <my-comp>
    <n slot="slot1">without span</n>
  </my-comp>
  
</html>

This will allow my page to show a <my-comp> with the inner HTML being my template text and then a <span> element containing my slotted in value of slotted value.

However, if the document had say a global CSS styling on <span>, I would see that styling on my inserted slotted value, when the template intended it to be just a string.

How might I be able to add just a text string without any CSS rules potentially applying to it? Currently I'm using an invalid element name <n> to get around the issue.



Solution 1:[1]

<slot> are styled by the container element, not (necessarily) by global CSS

In your example that was the global DOM:

<style>
<web-component>
  <span slot="slot1">
  --shadowRoot
    <slot name="slot1"> // styled by <style>

If you want to protect your <span>; you have to add one extra shadowRoot

<style>
<web-component>
  --shadowRoot
    <span slot="slot1">
      --shadowRoot
        <slot name="slot1"> // NOT styled by <style>

Note how I moved the <span> inside the first shadowRoot.

In the example below I used <div> instead of <span> for easier layout"
Also note where I added an extra <style>, because <slot> are styled by its container element

The Web Component creates two shadowRoots;
and MOVES the <style> and <div> elements inside the first shadowRoot
using: container.append(...this.children);

But the this.children are only available when the whole <my-comp> is parsed;
that is why a setTimeout is required

<style>
    div { border: 2px solid green }
</style>
<template id="my-template">
  <div>
    Web Component with <b>reflected</b> content: <slot name="slot1">default</slot>
  </div>
</template>

<div>Web Component with an extra shadowRoot:</div>
<my-comp>
  <!-- this content is "moved down" one shadowRoot -->
  <style>
    div { color: red /* styles slot within this <my-comp> */ }
  </style>
  <div slot="slot1"><h2>Forget R18! Web Components rule!</h2></div>
</my-comp>

<script>
  customElements.define("my-comp", class extends HTMLElement {
    constructor() {
      super().attachShadow({mode:'open'});
    }
    connectedCallback() {
      setTimeout(() => { // wait till innerHTML is parsed
        let container = document.createElement("span");
        this.shadowRoot.append(container);
        container.append(...this.children);
        container.attachShadow({mode:'open'})
                 .append(document.getElementById('my-template').content.cloneNode(true));
      });
    }
  })
</script>

Also note you might want to work with extra <template> tags because that extra <style> tag I added will style the global DOM for as long it is not moved yet to a shadowRoot... causing FOUCs.
But that would have complicated the above example.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1