What CSS Variables Can Do That Preprocessors Can’t

Mozilla Firefox recently implemented the highly anticipated CSS variables spec. Since then, some articles started appearing hinting that they are now largely obsolete with the advent and widespread use of preprocessors like Sass, Less, and Stylus. I would argue however that they provide something that preprocessors can’t, well at least can’t efficiently.

Preprocessor variables are static; compiling a Sass file with variables will always lead to the same, fixed CSS values in the output file. Native CSS variables however can change at runtime, allowing the implementation of things like theming to be done more elegantly. In addition to this, they’re also scoped, enabling a variable to be changed on only a subset of the DOM tree.

Let’s look at this in more detail.

Site Themes without CSS Variables

Let’s try to implement color themes for a site to illustrate the usefulness of CSS variables. Your task is to change various colors based on a class on the <body> element. In CSS without CSS variables you would do that something like:

.red-theme .primary-color {
  color: #801515;
}

.red-theme .primary-color-bg {
  background-color: #801515;
}

.red-theme .secondary-color {
  color: #D46A6A;
}

.red-theme .secondary-color-bg {
  background-color: #D46A6A;
}

.red-theme .tertiary-color {
  color: #AA3939;
}

.red-theme .tertiary-color-bg {
  background-color: #AA3939;
}

This results in 6 individual selectors in the resulting CSS – and that is just for one theme. In addition to that, you would need either the classes above peppered throughout the markup, or to add the explicit selectors into the theme styles CSS, both of which aren’t very maintainable or space-efficient solutions to the problem.

So this means, either:

<h1 class="tertiary-color">For Example</h1>
<button class="primary-color-bg">A button</button>
<a class="secondary-color">A link</a>

or:

.red-theme .primary-color-bg,
.red-theme button {
  background-color: #801515;
}

.red-theme .secondary-color,
.red-theme a {
  color: #D46A6A;
}

.red-theme .tertiary-color,
.red-theme h1 {
  color: #AA3939;
}

Site Themes with CSS Variables

Implementing this using variables is far more efficient. In such a case, you would define the color variables on .red-theme and then use them directly on your other styles. This will keep the style information out of your markup, where it doesn’t belong, and with the CSS selectors, where it does.

.red-theme {
  --theme-primary: #801515;
  --theme-secondary: #D46A6A;
  --theme-tertiary: #AA3939;
}

button, a {
  background-color: var(--theme-primary);
  color: var(--theme-secondary);
}

h1 {
  color: var(--theme-tertiary);
}

This is a far more elegant solution to the problem. In addition to this, as mentioned earlier, they’re scoped, so nothing is stopping us from using a different theme on say the navigation bar.

<body class="red-theme">
  <nav class="blue-theme">
    <button>Blue button</button>
  </nav>
  <main>
    <button>Red button</button>
  </main>
</body>

Beyond Site Themes

There are a bunch of applications beyond themes where CSS variables can extend the capabilities of preprocessors. Another example is implementing a spacing system similar to what Gmail has – allowing the user to select whether they want the UI to be compact, cosy, or comfortable.

.compact {
  --option-padding: 0.1em 0.2em;
}

.cosy {
  --option-padding: 0.3em 0.6em;
}

.comfortable {
  --option-padding: 0.5em 1em;
}

.list-item {
  padding: var(--option-padding);
}

Final Thoughts

I’ve shown here how to apply the upcoming CSS variables feature in a way that is distinct from the capabilities of preprocessor variables. Remember though that CSS variables are currently only enabled in the latest Firefox and Firefox for Android, so this is just a taste of what’s to come.

Win an Annual Membership to Learnable,

SitePoint's Learning Platform

  • Xam1311

    hi, with sass you can do the same with a sass control directive http://thesassway.com/intermediate/if-for-each-while i’ll post you an example on sassmeister this morning

    • LouisLazaris

      That would be great to see, thanks!

    • http://www.growingwiththeweb.com/ Daniel Imms

      When I say about efficiency, I’m talking about the size of your resulting CSS. Control directives are obviously just another way that write raw CSS. Unless there is another way I didn’t consider to do it with raw CSS?

  • Alex Walker

    Things might eventually change, but — for all their coolness — I feel like CSS variables are unworkable for the foreseeable future. If my CSS3 border-radius fails in IE8, big deal — I get square corners. If my CSS variable fails in IE8 I get NO color. That’s more than likely a complete and utter site-wide showstopper.

    I understand more and more browsers will support variables over time, but, as far as I can see, there’ll never be an elegant fallback for browsers that don’t. And to me that dooms the idea of CSS variables.

    • Olgierd Grzyb

      There is http://myth.io/ which polyfills CSS variables and some other modern stylesheets’ features. But I prefer LESS or Sass anyway ;)

      • http://www.growingwiththeweb.com/ Daniel Imms

        Nice find. It says it’s a preprocessor and ‘like’ a polyfill though, so it will suffer from the same issues as the other preprocessors (be static after compilation).

    • http://www.growingwiththeweb.com/ Daniel Imms

      I see your point, but there was a time when people felt just as hopeless about IE6 never going away. As more people move to the newer evergreen browsers I believe this will be less and less of a problem moving forward.

      I imagine some clever folks could also develop a polyfill for browsers that don’t support it. If all you need to worry about to support CSS variables widely is to include a script then their use would be much more appealing.

      • Alex Walker

        I thought about polyfills before I wrote the comment above — in fact I stopped and kicked off a discussion with some of the devs around me.

        Won’t any polyfill (Myth.io for example) just be Sass/Less by another name? You write a super-language that gets compiled to normal CSS. We *could* get JavaScript to polyfill old browsers at runtime (like Less did) , but that feels a bit brittle and puts more work on old browsers that are already struggling.

        Regardless, I thought it was a great article. These types of discussions are often as valuable as any article. Would love to read more.

  • Olgierd Grzyb

    Well, what’s the difference between this and writing:

    .compact { padding: .2em }
    .cosy { padding: .3em }
    .comfortable { padding: .5em }

    It’s even shorter, also LESS and Sass variables are scoped too (but it would be nice to have the feature in the language, of course!)

    • http://www.growingwiththeweb.com/ Daniel Imms

      What does the markup look like for these styles? How do you change the padding level on multiple elements? It’s all about trade offs, doing it will way will mean you have more markup to send to the user and need to change multiple classes on multiple elements to change the padding. The variable approach only requires a single class change on the body element.

      While they are scoped in the preprocessor, they aren’t in the generated CSS. The benefit of this I mention is that you could have different themes on different portions of the pages, all of which can be changed with a single element class change.

      • Olgierd Grzyb

        Ok, now I see the point. The problem could be solved using something like this:

        @mixin size() {
        .compact .cosy .comfortable }

        .list-item {
        @include size;
        }

        I consider the preference of solution (native or preprocessor) subjective. I personally prefer the preprocessor solution, as it seems to be more DRY.

        • http://www.growingwiththeweb.com/ Daniel Imms

          DRY in Sass, but it repeats itself quite a lot in CSS which could be an issue depending on how many times the mixin is used.

          • http://www.lanarea.eu Ken Verhaegen

            I reckon that a mixin might be repeating too much. That’s why there’s the “extend” functionality :) Simply extending .compact OR .cosy OR .comfortable might be a better solution than this…

            It’ll become this in the end:
            .cosy, .extended-div1, .extended-div2 { … }

          • fuddy duddy

            Are there plans to add real mixins to css natively? I’d get more use of those. Could even use mixins in place of variables.

  • Tim

    I guess I am in the minority here, but I like to have control over every single element in my CSS. I don’t really see a point to variables, especially when find and replace is so easy to do.

  • LouisLazaris

    Daniel Imms Not sure if you saw this yet, but Peter Gasston has posted this:

    http://www.broken-links.com/2014/08/28/css-variables-updating-custom-properties-javascript/

    Which references this article and describes another benefit to native variables.

  • Murray Chapman

    Crap. Even more to learn.