cssweb-componentshadow-domweb-frontendcss-variables

Why does CSS variables defined like `:where(:root, :host) { --my-var: value }` inside Shadow DOM not work?


I've noticed that some of the CSS variables I've defined within a web component (using Shadow DOM) aren't functioning as expected. It appears that using :where(:root, :host) is causing the issue. When I switch to :root, :host instead, the variables seem to work fine. Could anyone shed some light on why this is happening?

Here is the code. You can also see this in CodePen.

  <!-- 👇️ Define web component -->
  <script>
    class MyComponent1 extends HTMLElement {
      constructor() {
        super()
        this.attachShadow({ mode: 'open' }).innerHTML = `
          <style>
            :where(:root, :host) {
              --my-color: red;
            }
            span {
              color: var(--my-color);
            }
          </style>
          <span>MyComponent1 - This should be red</span>
        `
      }
    }
    
    /* 👇️ it works if you drop ":where" */
    class MyComponent2 extends HTMLElement {
      constructor() {
        super()
        this.attachShadow({ mode: 'open' }).innerHTML = `
          <style>
            :root, :host {
              --my-color: red;
            }
            span {
              color: var(--my-color);
            }
          </style>
          <span>MyComponent2 - This actually is red</span>
        `
      }
    }

    customElements.define('my-component1', MyComponent1)
    customElements.define('my-component2', MyComponent2)
  </script>

  <!-- 👇️ Render web components -->
  <my-component1></my-component1>
  <hr>
  <my-component2></my-component2>

I found that removing :where makes everything work as expected.

However, the issue is that the code is automatically generated by a CSS framework (Panda CSS), so I can't alter the variable definitions directly.

Here's the relevant line in the GitHub repository: https://github.com/chakra-ui/panda/blob/577dcb9d855c705e364aa2f662492f53e4034bfc/packages/generator/src/generator.ts#L11


Solution

  • From the CSS Scoping spec 3.2.2. Selecting Shadow Hosts from within a Shadow

    A shadow host is outside of the shadow tree it hosts, and so would ordinarily be untargettable by any selectors evaluated in the context of the shadow tree (as selectors are limited to a single tree), but it is sometimes useful to be able to style it from inside the shadow tree context.

    For the purpose of Selectors, a shadow host also appears in its shadow tree, with the contents of the shadow tree treated as its children. (In other words, the shadow host is treated as replacing the shadow root node.)

    When considered within its own shadow trees, the shadow host is featureless. Only the :host, :host(), and :host-context() pseudo-classes are allowed to match it.

    And the editor of the spec Tab Atkins points out:

    The host element not matching :where() is according to spec right now (nothing matches the element unless explicitly specified to do so), but :where() and :is() are definitely special-cases in that they're effectively selector grammar, not selectors themselves. I think it would be reasonable to allow them to match the host element, so long as one of their contained selectors does.

    So he thinks the spec should allow it, but it seems it doesn't in implementations at the moment.