February 10 2015

CSS Variables: An interim solution

Finally: CSS Variables are coming. Unfortunately they represent something of a dilemma for CSS’ fundamental principle of backwards compatibility. As Aron Gustafson notes, the current (and indeed, probably any hypothetical alternative) implementation of CSS variables simply can’t be done in a backwards-compatible way that doesn’t also require code duplication. So how can we make practical use of them today?

The stop-gap

These days, anyone whose CSS is sophisticated enough to benefit from variables is probably already using a CSS preprocessor like Sass. For these people in particular, CSS variables are even less necessary. However, these preprocessors also contain the seeds of a long-term solution. To illustrate by example, consider the humble border-radius property.

WebKit was the first browser engine to support this property, and so at first the only way to use it was via a vendor prefixed --webkit-border-radius rule. Once other browsers started to support border radius via their own prefixed property and (in particular) WebKit-based mobile browsers started to dominate the web, border radius started to rise in popularity. This presented three choices to developers: duplicate your code (once for each prefixed property), ignore non-WebKit browsers (sadly, a lot of people did this) or use a preprocessor library such as Compass that would handle all the prefixes for you. With the latter, you could simply write:

1div#news {
2  @include border-radius(5px);

…and it would generate the appropriate CSS:

1div#news {
2  --webkit-border-radius: 5px;
3  --moz-border-radius: 5px;
4  border-radius: 5px;

The great thing about this approach is you don’t need to care too much about exactly which vendor prefixes are necessary. Provided you’re not stuck delivering layouts that absolutely must be pixel perfect in Internet Explorer 6, as the browser state of the art changes, the CSS generated by the latest version of Compass will reflect that state of the art. In a few years time it’s conceivable that using @include border-radius(5px) would be functionally identical to writing border-radius: 5px. Meanwhile, you can focus on other, more challenging problems, safe in the knowledge that Compass has your back.

So how does this relate to CSS variables? Well, consider how we might use CSS variables in SCSS today. First, let’s define the two example variables Aron uses, with a SCSS map data structure (support for maps was introduced with Sass 3.3):

1$css-variables: (
2  foreground-color: #333,
3  background-color: #fff

We then need a mechanism for addressing the relevantly-named key when using a particular CSS variable, in this case with a SCSS mixin:

1$prefix: '--';
2@mixin css-variable($property, $name) {
3  #{$property}: map-get($css-variables, $name);
4  #{$property}: var(#{$prefix}#{$name});
5} // @mixin css-variable()

(The $prefix variable is to get around a current limitation with how SCSS interprets the string literal -- when interpolated alongside a variable name). Finally, we need to declare the CSS variables using the new syntax:

1:root {
2  @each $name, $value in $css-variables {
3    #{$prefix}#{$name}: $value;
4  }

This will generate a CSS variable declaration at the root level for each key in the Sass map. With the groundwork done, you can now make use of these variables like this:

1body {
2  @include css-variable(background, background-color);
3  @include css-variable(color, foreground-color);

The final rendered CSS then looks like this:

 1:root {
 2  --foreground-color: #333333;
 3  --background-color: white;
 6body {
 7  background: white;
 8  background: var(--background-color);
 9  color: #333333;
10  color: var(--foreground-color);

Why even bother doing this?

As you’re no doubt asking, this seems like a lot of work to create a bunch of redundant rules. Why not just use the preprocessor and be done with it? Good question! The answer lies in whether or not you feel like using native CSS variables will ever be a part of your workflow. If your project is likely to live and die before CSS variables become widely supported then stick with what you’re using today and be happy.

If, however, you think there’s some value in either learning more about how native variables can be incorporated into your existing CSS, or if you would like to eventually transition away from using a preprocessor within the lifetime of your project, this might be a useful stepping stone. Ultimately the problem of how to use CSS variables will be solved by the gradual disappearance of non-supporting browsers; by building variables into your workflow in this fashion the generated CSS can be a perfect starting point for a preprocessor-free codebase when that day finally arrives.