Search code examples
javascriptcssdomes6-modulesshadow-dom

How to use Shadow Dom with Javascript and CSS modules


EDIT 1

Thanks very much @Danny for the very useful answer. I guess my question has more to do with CSS sope and encapsulation. If I import a CSS StyleSheet into a module then I don't want that Stylesheet to bleed out into the parent and lightDOM of the caller. The effect I want to achieve is similar to this basic Custom Element In this case I build a STYLE element and attach it to the Extension class of HTMLElement.

I'm trying to achieve the same thing with Javascript/CSS modules. I will try with myDiv.part='shadowTree' but that seems to be defeating the whole purpose of ShadowDOM.

I'll go read up om SLOTs again but I only want to expose certain elements from my module call. This is what I thought #returnedDiv::part(styleableRuleException) wass, the rest of the CSS I expected to be forcably scoped to my JS module?

WRT MDN I think my problem is my imported StyleSheet is not a "constructed stylesheet"? Like:-

 let sheet = new CSSStyleSheet();

But Web.Dev says ok?

<EDIT 1

I am trying to use Shadow DOM in my Javascript module and also import a CSS module/sheet into the Javascript module: -

module.js

import sheet from '/styles.css' assert {type: 'css'};

export function message()
{
    const myDiv = document.createElement("div");
    const shadowRoot = myDiv.attachShadow({mode:"open"});
    document.adoptedStyleSheets = [sheet];
    myDiv.textContent = "This is Text";
    return myDiv;
}

As soon as I add the line to .attachShadow() the content of the DIV "This is Text" stopped appearing.

If I went further an tried to adoptStyleSheets to the shadowRoot instead of the document then no CSS from styles.css took effect.

styles.css

host:: {
    background-color: yellow;
}
div {
    background-color: red;
}

What do I have to do to be able to use Shadow DOM in my Modules in concert with Adopted Styles from CSS modules?

test_mod.html

<!DOCTYPE html>
<html>
<style type="text/css">
div div {
    height: 100px;
    width: 100px;
    text-align: center;
    background-color: lightblue;
}
</style>
<script type="module">
import {message} from "http://localhost/message.js";

document.getElementById("demo").appendChild(message());

</script>
<body>
<h1>JavaScript Modules</h1>

<div id="demo"></div>

</body>
</html>

Solution

  • As soon as I add the line to .attachShadow() the content of the DIV "This is Text" stopped appearing.

    Because you set content in lightDOM which will only be shown in shadowDOM when you <slot> it.

    If I went further an tried to adoptStyleSheets to the shadowRoot instead of the document then no CSS from styles.css took effect.

    The syntax is :host not host::

    Do read:

    Here is a playground with almost all shadowDOM goodies.

    JSFiddle: https://jsfiddle.net/WebComponents/bj4n1L0r/

    <h1>shadowDOM, loathed but loaded</h1>
    <script>
      function adoptSheet(atRoot, styles) {
        let sheet = new CSSStyleSheet();
        sheet.replaceSync(styles);
        atRoot.adoptedStyleSheets.push(sheet);
      }
      // Global CSS
      adoptSheet(document, "h1 { background: hotpink }" +
                           "div { background: pink }" +
                           "div::part(shadowH1) { background: lightgreen }");
    
      const myDiv = document.createElement("div");
      myDiv.setAttribute("border", "true");
      const shadowRoot = myDiv.attachShadow({ mode: "open" });
      adoptSheet(shadowRoot, ":host { background: red; padding: 5px }" +
                             ":host([border]) { border:2px dashed green }" +
                             "h1 { color: green }" +
                             "::slotted(h1) { color: blue }");
    
      myDiv.innerHTML =      "<h1                >I am in lightDOM</h1>";
      shadowRoot.innerHTML = "<h1 part='shadowH1'>I am in shadowDOM</h1>" +
                             "<slot>lightDOM will be slotted here</slot>";
    
      document.body.append(myDiv);
    </script>

    Notes

    • Global CSS styles BOTH <h1> in the main document background:hotpink, because slotted content is not moved to shadowDOM but reflected to shadowDOM
      See (long read): ::slotted CSS selector for nested children in shadowDOM slot

    • ::part in global CSS can style content inside shadowDOM
      https://developer.mozilla.org/en-US/docs/Web/CSS/::part

    • :host has no Specificity, so the shadowDOM is not background: red
      This is so (web) Component users can override default styling (see background:pink), if you do not want that, add an extra <div> layer in your shadowDOM and style that.

    • There are 2 types of (Web Component) Developers:
      Those who moan about shadowDOM, and those who learned to master shadowDOM