CSS Variables Instead of Attributes

With web components you can use attributes to configure its inner workings, controlling how it works and customizing different parts. You use styles to control how it looks, and even use CSS variables to configure its inner styling. Both attributes and styles are different and should not be used to do each others job. You would not use an attribute to help style some of the internal parts, for example. You could, but technically it is not the right tool for the job.

However, there are times when you would want to use CSS variables like normal attribute values. For example, imagine we have a web component that displays rotating circles and we can set the number of circles shown by setting the attribute "circle-count". It looks great on a PC screen (with 100 circles), but on a smartphone it looks too cluttered and needs to have the number of circles set to 20. Instead of an attribute, we could use a CSS variable "--circle-count" and depending on the screen size, just change the value.

Sounds great, but how do we do something like this. Let's look at how we would use an attribute first.

class TestAttribute extends HTMLElement {
  constructor() {
    // Must call super first
    super();

    // Attach shadow DOM root
    this.attachShadow({mode: 'open'});

    // Get attribute values
    const text = this.getAttribute('text');
    const number = this.getAttribute('number');

    // Set inner HTML of shadow DOM
    this.shadowRoot.innerHTML =
    `<div>Attributes<br>Text: ${text}<br>Number: ${number}</div>`;
  }
}  

In this example, we get the value of the attributes and use them to create the inner HTML. This is the basic way of handling these attributes, nothing special going on here.

class TestCssVar extends HTMLElement {
  constructor() {
    // Must call super first
    super();

    // Attach shadow DOM root
    this.attachShadow({mode: 'open'});

    // Get styles (they may be inline or in a class somewhere)
    const cssStyleDeclaraction = window.getComputedStyle(this);

    // Get the text and number variables
    const text = cssStyleDeclaraction.getPropertyValue('--text');
    const number = cssStyleDeclaraction.getPropertyValue('--number');

    // Set inner HTML of shadow DOM
    this.shadowRoot.innerHTML =
    `<div>CSS Variables<br>Text: ${text}<br>Number: ${number}</div>`;
  }
}

Here we are going to use the CSS variables --text and --number. There are two method for getting these values. There is the direct method of getting the CSS variable using the element's style.getPropertyValue function. This only works if the web component's element has had the CSS variables set directly, within an inline style. This will not work if the CSS variables are set within a style class.

The best way is to use the windows.getComputedStyle function. This works out all the current styles linked to an element, which also includes all the CSS variables. We can then use this, and the getPropertyValue function, to get the current CSS variable values we want.

Now let's take a look at how this could be useful.

<style>
  .test {
    display: grid;
    grid-template-columns: 1fr;
    grid-template-rows: 5rem, 5rem;
  }

  .attribute {
    grid-column: 1;
    grid-row: 1;
    background-color: lightblue;
    color: black;
  }

  .css-var {
    grid-column: 1;
    grid-row: 2;
    background-color: darkorange;
    color: white;
    --text: 'CSS Test 1';
    --number: 123;
  }

  @media (max-width: 80rem) {
    .css-var {
      --text: 'CSS Test 2';
      --number: 80;
    }
  }
  @media (max-width: 60rem) {
    .css-var {
      --text: 'CSS Test 3';
      --number: 60;
    }
  }
  @media (max-width: 40rem) {
    .css-var {
      --text: 'CSS Test 4';
      --number: 40;
    }
  }
</style>
<p>...</p>
<div class="test">
  <test-attribute
    class="attribute"
    text="Attribute Test 1"
    number="987">
  </test-attribute>
  <test-css-var class="css-var">
  </test-css-var>
</div>

We have our two test web components. The first is using standard attributes to set the values we want to display. The second is using the class css-var which have the CSS variables we want to show, but they change depending on the size of the page. Take a look at it up and running.

The values shown will not change until the page is reloaded. If you can, change the size of the page and reload it. You will see that the --text and --number CSS variable values change depending the width.

I cannot see this being used all that often but there can be times when it is the best method to use. Take a look at the <background-rotating-fans> web component for an example of this being used.

One issue with this method is that you have no way of knowing if the CSS variable changes. We are able to monitor changes to attributes, just not CSS information. However, in the above example, we could have used the resize event, rechecked the CSS variables and updated the information shown.