Search code examples
cssweb-componentshadow-domcustom-elementcss-variables

CSS Custom variables in custom element and shadow dom not resolved by IDE or browser


I have been trying to use CSS custom properties within a custom element, like so:

function tpl(raw){
  const template = document.createElement('template');
  template.innerHTML = raw;
  return template;
}

const templateNode = tpl`
  <style>
  :root {
    --default-text-color: red;  // IDE squiggles
  }
  
  p { color: var(--default-text-color); } 
  </style>

  <p>Test</p>
`

class MyElement extends HTMLElement {
  constructor() {
    super();

    const sd = this.attachShadow({ mode: 'open' });
    sd.appendChild(templateNode.content.cloneNode(true))
  }

  connectedCallback(){
    console.log('my-element connected')
  }
}

customElements.define('my-element', MyElement)

The custom property: --default-text-color doesn't seem to be recognized by IDE and it is not resolved by the browser.

squiggles

So apparently I am missing some crucial bit of info as to why the above code is not supposed to work. Can anyone explain?

NB. I am aware of Constructable StyleSheets and its adoptedStyleSheets member, but it only seems to be a proposed solution at this moment, not fully supported by all browsers yet. I am hoping to find a solution which works in all browsers that support css custom properties and custom elements.


Solution

  • As mentioned by Myf in the comments, your use of :root instead of :host is your problem.

    You can use :root at document level (or any CSS selector),
    because CSS properties cascade into shadowDOM.

    (unrelated) You are also overcomplicating code; but you can't be blamed,
    all blogs and even MDN documentation show bloated code

    customElements.define('my-element', class extends HTMLElement {
      constructor() {
        super() // sets AND returns 'this' scope
          .attachShadow({ mode: 'open'}) // sets AND returns 'this.shadowRoot'
          .innerHTML = `<style>
                          :host{ --textcolor: green }
                          p { color: var(--textcolor,red); background: var(--bgcolor,grey) } 
                        </style>
                        <h1>Hello Web Component</h1>`
      }
    })
    :root {
      --bgcolor: lightgreen;
    }
    <my-element></my-element>

    text added by Trusktr:

    https://stackoverflow.com/revisions/68934167/4

    Note if you do it that way, --textcolor is no longer inheritable from an ancestor element that is above the custom element to be styled.

    Placing your vars in :root is one way to keep them inheritable, but not ideal for code inside a custom element.

    Mapping them to "internal" vars is another way we can do this so that we can keep properties inheritable:

    :host {
      --textcolor--: var(--textcolor, red);
    }
    

    Then use --textcolor-- (using a convention of two trailing slashes to mark a property as "internal") where the style is needed:

    p {
      color: var(--textcolor--);
    }
    

    Now --textcolor is inheritable because it will not be overriden by a value on :host.