Search code examples
javascriptcssweb-componentcss-variables

WebComponents - what's the advantage of using CSS Custom Properties in the Component Public Interface?


A WebComponent may include CSS Custom Properties in its encapsulated styles.

This gives consumers of the component a way to customise that component's styles:

Declaring

--fancy-tabs-bg: red

in the main styles means that when the shadow-root styles include

background-color: var(--fancy-tabs-bg, #9E9E9E)

the background-color will be red (or whatever value --fancy-tabs-bg is set to).


But... I note in the same article it explicitly states:

One gotcha with :host is that rules in the parent page have higher specificity than :host rules defined in the element. That is, outside styles win. This allows users to override your top-level styling from the outside.

and again, later:

Outside styles always win over styles defined in shadow DOM. For example, if the user writes the selector fancy-tabs { width: 500px; }, it will trump the component's rule: :host { width: 650px;}

So... instead of declaring a value for --fancy-tabs-bg we could just... set a value for background-color (?)

Really? Let's find out.

Here's a WebComponent (largely copied from the article referenced above) where the first two instances of the component are styled using a CSS Custom Property (ie. --fancy-tabs-bg) and the third instance is styled directly, using the relevant CSS Property (ie. background-color).

class FancyTabs extends HTMLElement {
  
  constructor() {
    super();
    this.root = this.attachShadow({mode: "open"});
  }

  connectedCallback() {
  
    this.root.innerHTML = `
    
    <style>
      :host {
        display: block;
        width: 100px;
        height: 100px;
        margin: 6px;
        border-radius: 10px;
      }
      
      :host([background]) {
        background-color: var(--fancy-tabs-bg, #9E9E9E);
      }
    </style>
    
    `;
  }
}

customElements.define('fancy-tabs', FancyTabs);
fancy-tabs {
  float: left;
}

fancy-tabs:nth-of-type(1) {
  --fancy-tabs-bg: red;
}

fancy-tabs:nth-of-type(2) {
  --fancy-tabs-bg: orange;
}

fancy-tabs:nth-of-type(3) {
  background-color: green;
}
<fancy-tabs background>...</fancy-tabs>
<fancy-tabs background>...</fancy-tabs>
<fancy-tabs background>...</fancy-tabs>

There's no difference, is there?

So why use CSS Custom Properties at all? Why highlight in the public interface that a particular CSS property is available for user-customisation? Surely all CSS properties are available for user-customisation, aren't they?

What am I missing?


Solution

  • You won't notice any difference when dealing with the host element but you can clearly see the use of CSS variables when having more elements inside:

    Example where CSS variables is useful to update the styling of nested elements. I doubt you can find a better way without CSS variables.

    class FancyTabs extends HTMLElement {
      constructor() {
        super();
        this.shadow = this.attachShadow({ mode: 'closed' });
        const css = `
         :host {
            display: block;
            width: 100px;
            height: 100px;
            margin: 6px;
            border-radius: 10px;
          }
          
         :host([background]) {
            background-color: var(--fancy-tabs-bg, #9E9E9E);
          }
          div {
            padding: var(--p,0px);
            border:var(--b,0px) solid;
          }`
        this.styles = document.createElement('style');
        this.styles.innerHTML = css;
      }
      connectedCallback() {
        const div = document.createElement('div');
        div.innerHTML = this.innerHTML;
        this.shadow.appendChild(this.styles);
        this.shadow.appendChild(div);
      }
    }
    
    customElements.define('fancy-tabs', FancyTabs);
    fancy-tabs {
      float: left;
    }
    
    fancy-tabs:nth-of-type(1) {
      --fancy-tabs-bg: red;
      --p:20px;
      --b:5px;
    }
    
    fancy-tabs:nth-of-type(2) {
      --fancy-tabs-bg: orange;
      --p:10px;
      --b:3px;
    }
    fancy-tabs:nth-of-type(3) {
      --fancy-tabs-bg: orange;
      padding:20px; /* will get applied to host*/
      border-width:5px; /* will do nothing */
    }
    <fancy-tabs background>text  here</fancy-tabs>
    <fancy-tabs background>text  here</fancy-tabs>
    <fancy-tabs background>text  here</fancy-tabs>