Search code examples
web-componentshadow-domnative-web-componentshadow-root

How to change css variable in shadow dom (web component)


I can't manage to change a css variable (--color) because the shadowRoot querySelector won't return :host or html

<style>
    :host {display: block;
        --color: ${this.color};
    }
    .colored {
        color: var(--color);
    }
</style>

This breaks (querySelector returns null)

this.shadowRoot.querySelector(':host').style.setProperty('--color', value);

This changes the color but is not want I want

this.shadowRoot.querySelector('.colored').style.setProperty('color', value); 

And finally, this attempt does the trick, but it will only work as long as :host is the first rule and it is in the first stylesheet.

this.shadowRoot.styleSheets[0].rules[0].style.setProperty('--color', value);

<!DOCTYPE html>

<body>
    <my-element></my-element>
    <script>
        class MyElement extends HTMLElement {
            constructor() {
                super();
                this._color = "green";
                this._shadowRoot = this.attachShadow({ mode: "open" });
                this._shadowRoot.innerHTML = '';
                this._shadowRoot.appendChild(this.template().content.cloneNode(true));
            }
            template() {
                const template = document.createElement("template");
                template.innerHTML = `
<style>
    :host {display: block;
        --color: ${this.color};
    }
    .colored {
        color: var(--color);
    }
</style>
            <p>The color will change in 2 seconds: <span id="color" class="colored">${this.color}</span></p>
        `;
                return template;
            }
            get color() {
                return this._color;
            }
            set color(value) {
                this._color = value;
                this.shadowRoot.getElementById('color').innerHTML = value;
                // ▼ 👉🏻 Uncomment below. Cannot change --color variable; selector returns null
                // this.shadowRoot.querySelector(':host').setProperty('--color', value); 

                // ▼ This works but is not what I want. I need to change the variable
                 //this.shadowRoot.querySelector('.colored').style.setProperty('color', value); 

                 // ▼ This is subject to the firts stylesheet and :host being first rule
                 this.shadowRoot.styleSheets[0].rules[0].style.setProperty('--color', value);
            }

        }
        customElements.define("my-element", MyElement);
        setTimeout(() => {
            document.querySelector('my-element').color = 'blue';
        }, 2000);
    </script>
</body>

</html>

Thanks!


Solution

  • :host refers to your <my-element>

    So set properties on your my-element with: this.style.setProperty('--color', value);

    <my-element></my-element>
    <script>
      customElements.define("my-element", class extends HTMLElement {
        constructor() {
          super()
            .attachShadow({mode: "open"})
            .innerHTML = 
             `<style>
                 :host    { --color: red }
                 .colored { color: var(--color) }
              </style>
              <p>The color will change in 2 seconds: <span class="colored">${this.color}</span></p>`;
           this.color = "green";
        }
        get color() {
          return this._color;
        }
        set color(value) {
          this._color = value;
          this.shadowRoot.querySelector('.colored').innerHTML = value;
          this.style.setProperty('--color', value); 
        }
      });
      
      setTimeout(() => document.querySelector('my-element').color = 'blue', 2000);
      
    </script>