Menu Search
Jump to the content X X
Smashing Conf New York

You know, we use ad-blockers as well. We gotta keep those servers running though. Did you know that we publish useful books and run friendly conferences — crafted for pros like yourself? E.g. our upcoming SmashingConf New York, dedicated to smart front-end techniques and design patterns.

It’s Time To Start Using CSS Custom Properties

Today, CSS preprocessors are a standard for web development. One of the main advantages of preprocessors is that they enable you to use variables. This helps you to avoid copying and pasting code, and it simplifies development and refactoring.

We use preprocessors to store colors, font preferences, layout details — mostly everything we use in CSS.

But preprocessor variables have some limitations:

  • You cannot change them dynamically.
  • They are not aware of the DOM’s structure.
  • They cannot be read or changed from JavaScript.

As a silver bullet for these and other problems, the community invented CSS custom properties. Essentially, these look and work like CSS variables, and the way they work is reflected in their name.

Custom properties are opening new horizons for web development.

Further Reading on SmashingMag: Link

Syntax To Declare And Use Custom Properties Link

The usual problem when you start with a new preprocessor or framework is that you have to learn a new syntax.

Each preprocessor requires a different way of declaring variables. Usually, it starts with a reserved symbol — for example, $ in Sass and @ in LESS.

CSS custom properties have gone the same way and use -- to introduce a declaration. But the good thing here is that you can learn this syntax once and reuse it across browsers!

You may ask, “Why not reuse an existing syntax?”

There is a reason5. In short, it’s to provide a way for custom properties to be used in any preprocessor. This way, we can provide and use custom properties, and our preprocessor will not compile them, so the properties will go directly to the outputted CSS. And, you can reuse preprocessor variables in the native ones, but I will describe that later.

(Regarding the name: Because their ideas and purposes are very similar, sometimes custom properties are called the CSS variables, although the correct name is CSS custom properties, and reading further, you will understand why this name describes them best.)

So, to declare a variable instead of a usual CSS property such as color or padding, just provide a custom-named property that starts with --:

.box{
  --box-color: #4d4e53;
  --box-padding: 0 10px;
}

The value of a property may be any valid CSS value: a color, a string, a layout value, even an expression.

Here are examples of valid custom properties:

:root{
  --main-color: #4d4e53;
  --main-bg: rgb(255, 255, 255);
  --logo-border-color: rebeccapurple;

  --header-height: 68px;
  --content-padding: 10px 20px;

  --base-line-height: 1.428571429;
  --transition-duration: .35s;
  --external-link: "external link";
  --margin-top: calc(2vh + 20px);

  /* Valid CSS custom properties can be reused later in, say, JavaScript. */
  --foo: if(x > 5) this.width = 10;
}

In case you are not sure what :root6 matches, in HTML it’s the same as html but with a higher specificity.

As with other CSS properties, custom ones cascade in the same way and are dynamic. This means they can be changed at any moment and the change is processed accordingly by the browser.

To use a variable, you have to use the var() CSS function and provide the name of the property inside:

.box{
  --box-color:#4d4e53;
  --box-padding: 0 10px;

  padding: var(--box-padding);
}

.box div{
  color: var(--box-color);
}

Declaration and Use Cases Link

The var() function is a handy way to provide a default value. You might do this if you are not sure whether a custom property has been defined and want to provide a value to be used as a fallback. This can be done easily by passing the second parameter to the function:

.box{
  --box-color:#4d4e53;
  --box-padding: 0 10px;

  /* 10px is used because --box-margin is not defined. */
  margin: var(--box-margin, 10px);
}

As you might expect, you can reuse other variables to declare new ones:

.box{
  /* The --main-padding variable is used if --box-padding is not defined. */
  padding: var(--box-padding, var(--main-padding));

  --box-text: 'This is my box';

  /* Equal to --box-highlight-text:'This is my box with highlight'; */
  --box-highlight-text: var(--box-text)' with highlight';
}

Operations: +, -, *, / Link

As we got accustomed to with preprocessors and other languages, we want to be able to use basic operators when working with variables. For this, CSS provides a calc() function, which makes the browser recalculate an expression after any change has been made to the value of a custom property:

:root{
  --indent-size: 10px;

  --indent-xl: calc(2*var(--indent-size));
  --indent-l: calc(var(--indent-size) + 2px);
  --indent-s: calc(var(--indent-size) - 2px);
  --indent-xs: calc(var(--indent-size)/2);
}

A problem awaits if you try to use a unit-less value. Again, calc() is your friend, because without it, it won’t work:

:root{
  --spacer: 10;
}

.box{
  padding: var(--spacer)px 0; /* DOESN'T work */
  padding: calc(var(--spacer)*1px) 0; /* WORKS */
}

Scope And Inheritance Link

Before talking about CSS custom property scopes, let’s recall JavaScript and preprocessor scopes, to better understand the differences.

We know that with, for example, JavaScript variables (var), a scope is limited to the functions.

We have a similar situation with let and const, but they are block-scope local variables.

A closure in JavaScript is a function that has access to the outer (enclosing) function’s variables — the scope chain. The closure has three scope chains, and it has access to the following:

  • its own scope (i.e. variables defined between its braces),
  • the outer function’s variables,
  • the global variables.
7
(View large version8)

The story with preprocessors is similar. Let’s use Sass as an example because it’s probably the most popular preprocessor today.

With Sass, we have two types of variables: local and global.

A global variable can be declared outside of any selector or construction (for example, as a mixin). Otherwise, the variable would be local.

Any nested blocks of code can access the enclosing variables (as in JavaScript).

9
(View large version10)

This means that, in Sass, the variable’s scopes fully depend on the code’s structure.

However, CSS custom properties are inherited by default, and like other CSS properties, they cascade.

You also cannot have a global variable that declares a custom property outside of a selector — that’s not valid CSS. The global scope for CSS custom properties is actually the :root scope, whereupon the property is available globally.

Let’s use our syntax knowledge and adapt the Sass example to HTML and CSS. We’ll create a demo using native CSS custom properties. First, the HTML:

global
<div class="enclosing">
  enclosing
  <div class="closure">
    closure
  </div>
</div>

And here is the CSS:

:root {
  --globalVar: 10px;
}

.enclosing {
  --enclosingVar: 20px;
}

.enclosing .closure {
  --closureVar: 30px;

  font-size: calc(var(--closureVar) + var(--enclosingVar) + var(--globalVar));
  /* 60px for now */
}

See the Pen css-custom-properties-time-to-start-using 111 by Serg Hospodarets (@malyw4035302421181512) on CodePen4136312522191613.

Changes to Custom Properties Are Immediately Applied to All Instances Link

So far, we haven’t seen how this is any different from Sass variables. However, let’s reassign the variable after its usage:

In the case of Sass, this has no effect:

.closure {
  $closureVar: 30px; // local variable
  font-size: $closureVar +$enclosingVar+ $globalVar;
  // 60px, $closureVar: 30px is used

  $closureVar: 50px; // local variable
}

See the Pen css-custom-properties-time-to-start-using 314 by Serg Hospodarets (@malyw4035302421181512) on CodePen4136312522191613.

But in CSS, the calculated value is changed, because the font-size value is recalculated from the changed --closureVar value:

.enclosing .closure {
  --closureVar: 30px;

  font-size: calc(var(--closureVar) + var(--enclosingVar) + var(--globalVar));
  /* 80px for now, --closureVar: 50px is used */

  --closureVar: 50px;
}

See the Pen css-custom-properties-time-to-start-using 217 by Serg Hospodarets (@malyw4035302421181512) on CodePen4136312522191613.

That’s the first huge difference: If you reassign a custom property’s value, the browser will recalculate all variables and calc() expressions where it’s applied.

Preprocessors Are Not Aware of the DOM’s Structure Link

Suppose we wanted to use the default font-size for the block, except where the highlighted class is present.

Here is the HTML:

<div class="default">
  default
</div>

<div class="default highlighted">
  default highlighted
</div>

Let’s do this using CSS custom properties:

.highlighted {
  --highlighted-size: 30px;
}

.default {
  --default-size: 10px;

  /* Use default-size, except when highlighted-size is provided. */
  font-size: var(--highlighted-size, var(--default-size));
}

Because the second HTML element with the default class carries the highlighted class, properties from the highlighted class will be applied to that element.

In this case, it means that --highlighted-size: 30px; will be applied, which in turn will make the font-size property being assigned use the --highlighted-size.

Everything is straightforward and works:

See the Pen css-custom-properties-time-to-start-using 420 by Serg Hospodarets (@malyw4035302421181512) on CodePen4136312522191613.

Now, let’s try to achieve the same thing using Sass:

.highlighted {
  $highlighted-size: 30px;
}

.default {
  $default-size: 10px;

  /* Use default-size, except when highlighted-size is provided. */
  @if variable-exists(highlighted-size) {
    font-size: $highlighted-size;
  }
  @else {
    font-size: $default-size;
  }
}

The result shows that the default size is applied to both:

See the Pen css-custom-properties-time-to-start-using 523 by Serg Hospodarets (@malyw4035302421181512) on CodePen4136312522191613.

This happens because all Sass calculations and processing happen at compilation time, and of course, it doesn’t know anything about the DOM’s structure, relying fully on the code’s structure.

As you can see, custom properties have the advantages of variables scoping and add the usual cascading of CSS properties, being aware of the DOM’s structure and following the same rules as other CSS properties.

The second takeaway is that CSS custom properties are aware of the DOM’s structure and are dynamic.

CSS-Wide Keywords And The all Property Link

CSS custom properties are subject to the same rules as the usual CSS custom properties. This means you can assign any of the common CSS keywords to them:

  • inherit
    This CSS keyword applies the value of the element’s parent.
  • initial
    This applies the initial value as defined in the CSS specification (an empty value, or nothing in some cases of CSS custom properties).
  • unset
    This applies the inherited value if a property is normally inherited (as in the case of custom properties) or the initial value if the property is normally not inherited.
  • revert
    This resets the property to the default value established by the user agent’s style sheet (an empty value in the case of CSS custom properties).

Here is an example:

.common-values{
  --border: inherit;
  --bgcolor: initial;
  --padding: unset;
  --animation: revert;
}

Let’s consider another case. Suppose you want to build a component and want to be sure that no other styles or custom properties are applied to it inadvertently (a modular CSS solution would usually be used for styles in such a case).

But now there is another way: to use the all CSS property26. This shorthand resets all CSS properties.

Together with CSS keywords, we can do the following:

.my-wonderful-clean-component{
  all: initial;
}

This reset all styles for our component.

Unfortunately, the all keyword doesn’t reset custom properties27. There is an ongoing discussion about whether to add the -- prefix28, which would reset all CSS custom properties.

So, in future, a full reset might be done like this:

.my-wonderful-clean-component{
  --: initial; /* reset all CSS custom properties */
  all: initial; /* reset all other CSS styles */
}

CSS Custom Properties Use Cases Link

There are many uses of custom properties. I will show the most interesting of them.

Emulate Non-Existent CSS Rules Link

The name of these CSS variables is “custom properties,” so why not to use them to emulate non-existent properties?

There are many of them: translateX/Y/Z, background-repeat-x/y (still not cross-browser compatible), box-shadow-color.

Let’s try to make the last one work. In our example, let’s change the box-shadow’s color on hover. We just want to follow the DRY rule (don’t repeat yourself), so instead of repeating box-shadow’s entire value in the :hover section, we’ll just change its color. Custom properties to the rescue:

.test {
  --box-shadow-color: yellow;
  box-shadow: 0 0 30px var(--box-shadow-color);
}

.test:hover {
  --box-shadow-color: orange;
  /* Instead of: box-shadow: 0 0 30px orange; */
}

See the Pen Emulating “box-shadow-color” CSS property using CSS Custom Properties29 by Serg Hospodarets (@malyw4035302421181512) on CodePen4136312522191613.

Color Themes Link

One of the most common use cases of custom properties is for color themes in applications. Custom properties were created to solve just this kind of problem. So, let’s provide a simple color theme for a component (the same steps could be followed for an application).

Here is the code for our button component32:

.btn {
  background-image: linear-gradient(to bottom, #3498db, #2980b9);
  text-shadow: 1px 1px 3px #777;
  box-shadow: 0px 1px 3px #777;
  border-radius: 28px;
  color: #ffffff;
  padding: 10px 20px 10px 20px;
}

Let’s assume we want to invert the color theme.

The first step would be to extend all of the color variables to CSS custom properties and rewrite our component. So, the result would be the same33:

.btn {
  --shadow-color: #777;
  --gradient-from-color: #3498db;
  --gradient-to-color: #2980b9;
  --color: #ffffff;

  background-image: linear-gradient(
    to bottom,
    var(--gradient-from-color),
    var(--gradient-to-color)
  );
  text-shadow: 1px 1px 3px var(--shadow-color);
  box-shadow: 0px 1px 3px var(--shadow-color);
  border-radius: 28px;
  color: var(--color);
  padding: 10px 20px 10px 20px;
}

This has everything we need. With it, we can override the color variables to the inverted values and apply them when needed. We could, for example, add the global inverted HTML class (to, say, the body element) and change the colors when it’s applied:

body.inverted .btn{
  --shadow-color: #888888;
  --gradient-from-color: #CB6724;
  --gradient-to-color: #D67F46;
  --color: #000000;
}

Below is a demo in which you can click a button to add and remove a global class:

See the Pen css-custom-properties-time-to-start-using 934 by Serg Hospodarets (@malyw4035302421181512) on CodePen4136312522191613.

This behavior cannot be achieved in a CSS preprocessor without the overhead of duplicating code. With a preprocessor, you would always need to override the actual values and rules, which always results in additional CSS.

With CSS custom properties, the solution is as clean as possible, and copying and pasting is avoided, because only the values of the variables are redefined.

Using Custom Properties With JavaScript Link

Previously, to send data from CSS to JavaScript, we often had to resort to tricks37, writing CSS values via plain JSON in the CSS output and then reading it from the JavaScript.

Now, we can easily interact with CSS variables from JavaScript, reading and writing to them using the well-known .getPropertyValue() and .setProperty() methods, which are used for the usual CSS properties:

/**
* Gives a CSS custom property value applied at the element
* element {Element}
* varName {String} without '--'
*
* For example:
* readCssVar(document.querySelector('.box'), 'color');
*/
function readCssVar(element, varName){
  const elementStyles = getComputedStyle(element);
  return elementStyles.getPropertyValue(`--${varName}`).trim();
}

/**
* Writes a CSS custom property value at the element
* element {Element}
* varName {String} without '--'
*
* For example:
* readCssVar(document.querySelector('.box'), 'color', 'white');
*/
function writeCssVar(element, varName, value){
  return element.style.setProperty(`--${varName}`, value);
}

Let’s assume we have a list of media-query values:

.breakpoints-data {
  --phone: 480px;
  --tablet: 800px;
}

Because we only want to reuse them in JavaScript — for example, in Window.matchMedia()38 — we can easily get them from CSS:

const breakpointsData = document.querySelector('.breakpoints-data');

// GET
const phoneBreakpoint = getComputedStyle(breakpointsData)
  .getPropertyValue('--phone');

To show how to assign custom properties from JavaScript, I’ve created an interactive 3D CSS cube demo that responds to user actions.

It’s not very hard. We just need to add a simple background, and then place five cube faces with the relevant values for the transform property: translateZ(), translateY(), rotateX() and rotateY().

To provide the right perspective, I added the following to the page wrapper:

#world{
  --translateZ:0;
  --rotateX:65;
  --rotateY:0;

  transform-style:preserve-3d;
  transform:
    translateZ(calc(var(--translateZ) * 1px))
    rotateX(calc(var(--rotateX) * 1deg))
    rotateY(calc(var(--rotateY) * 1deg));
}

The only thing missing is the interactivity. The demo should change the X and Y viewing angles (--rotateX and --rotateY) when the mouse moves and should zoom in and out when the mouse scrolls (--translateZ).

Here is the JavaScript that does the trick:

// Events
onMouseMove(e) {
  this.worldXAngle = (.5 - (e.clientY / window.innerHeight)) * 180;
  this.worldYAngle = -(.5 - (e.clientX / window.innerWidth)) * 180;
  this.updateView();
};

onMouseWheel(e) {
  /*…*/

  this.worldZ += delta * 5;
  this.updateView();
};

// JavaScript -> CSS
updateView() {
  this.worldEl.style.setProperty('--translateZ', this.worldZ);
  this.worldEl.style.setProperty('--rotateX', this.worldXAngle);
  this.worldEl.style.setProperty('--rotateY', this.worldYAngle);
};

Now, when the user moves their mouse, the demo changes the view. You can check this by moving your mouse and using mouse wheel to zoom in and out:

See the Pen css-custom-properties-time-to-start-using 1039 by Serg Hospodarets (@malyw4035302421181512) on CodePen4136312522191613.

Essentially, we’ve just changed the CSS custom properties’ values. Everything else (the rotating and zooming in and out) is done by CSS.

Tip: One of the easiest ways to debug a CSS custom property value is just to show its contents in CSS generated content (which works in simple cases, such as with strings), so that the browser will automatically show the current applied value:

body:after {
  content: '--screen-category : 'var(--screen-category);
}

You can check it in the plain CSS demo42 (no HTML or JavaScript). (Resize the window to see the browser reflect the changed CSS custom property value automatically.)

Browser Support Link

CSS custom properties are supported in all major browsers43:

44
(View large version45)

This means that, you can start using them natively.

If you need to support older browsers, you can learn the syntax and usage examples and consider possible ways of switching or using CSS and preprocessor variables in parallel.

Of course, we need to be able to detect support in both CSS and JavaScript in order to provide fallbacks or enhancements.

This is quite easy. For CSS, you can use a @supports condition46 with a dummy feature query:

@supports ( (--a: 0)) {
  /* supported */
}

@supports ( not (--a: 0)) {
  /* not supported */
}

In JavaScript, you can use the same dummy custom property with the CSS.supports() static method:

const isSupported = window.CSS &&
    window.CSS.supports && window.CSS.supports('--a', 0);

if (isSupported) {
  /* supported */
} else {
  /* not supported */
}

As we saw, CSS custom properties are still not available in every browser. Knowing this, you can progressively enhance your application by checking if they are supported.

For instance, you could generate two main CSS files: one with CSS custom properties and a second without them, in which the properties are inlined (we will discuss ways to do this shortly).

Load the second one by default. Then, just do a check in JavaScript and switch to the enhanced version if custom properties are supported:

<!-- HTML -->
<link href="without-css-custom-properties.css"
    rel="stylesheet" type="text/css" media="all" />
// JavaScript
if(isSupported){
  removeCss('without-css-custom-properties.css');
  loadCss('css-custom-properties.css');
  // + conditionally apply some application enhancements
  // using the custom properties
}

This is just an example. As you’ll see below, there are better options.

How To Start Using Them Link

According to a recent survey47, Sass continues to be the preprocessor of choice for the development community.

So, let’s consider ways to start using CSS custom properties or to prepare for them using Sass.

We have a few options.

1. Manually Check in the Code for Support Link

One advantage of this method of manually checking in the code whether custom properties are supported is that it works and we can do it right now (don’t forget that we have switched to Sass):

$color: red;
:root {
  --color: red;
}

.box {
  @supports ( (--a: 0)) {
    color: var(--color);
  }
  @supports ( not (--a: 0)) {
    color: $color;
  }
}

This method does have many cons, not least of which are that the code gets complicated, and copying and pasting become quite hard to maintain.

2. Use a Plugin That Automatically Processes the Resulting CSS Link

The PostCSS ecosystem provides dozens of plugins today. A couple of them process custom properties (inline values) in the resulting CSS output and make them work, assuming you provide only global variables (i.e. you only declare or change CSS custom properties inside the :root selector(s)), so their values can be easily inlined.

An example is postcss-custom-properties48.

This plugin offers several pros: It makes the syntax work; it is compatible with all of PostCSS’ infrastructure; and it doesn’t require much configuration.

There are cons, however. The plugin requires you to use CSS custom properties, so you don’t have a path to prepare your project for a switch from Sass variables. Also, you won’t have much control over the transformation, because it’s done after the Sass is compiled to CSS. Finally, the plugin doesn’t provide much debugging information.

3. css-vars Mixin49 Link

I started using CSS custom properties in most of my projects and have tried many strategies:

  • Switch from Sass to PostCSS with cssnext50.
  • Switch from Sass variables to pure CSS custom properties.
  • Use CSS variables in Sass to detect whether they are supported.

As a result of that experience, I started looking for a solution that would satisfy my criteria:

  • It should be easy to start using with Sass.
  • It should be straightforward to use, and the syntax must be as close to native CSS custom properties as possible.
  • Switching the CSS output from the inlined values to the CSS variables should be easy.
  • A team member who is familiar with CSS custom properties would be able to use the solution.
  • There should be a way to have debugging information about edge cases in the usage of variables.

As a result, I created css-vars, a Sass mixin that you can find on Github51. Using it, you can sort of start using CSS custom properties syntax.

Using css-vars Mixin Link

To declare variable(s), use the mixin as follows:

$white-color: #fff;
$base-font-size: 10px;

@include css-vars((
  --main-color: #000,
  --main-bg: $white-color,
  --main-font-size: 1.5*$base-font-size,
  --padding-top: calc(2vh + 20px)
));

To use these variables, use the var() function:

body {
  color: var(--main-color);
  background: var(--main-bg, #f00);
  font-size: var(--main-font-size);
  padding: var(--padding-top) 0 10px;
}

This gives you a way to control all of the CSS output from one place (from Sass) and start getting familiar with the syntax. Plus, you can reuse Sass variables and logic with the mixin.

When all of the browsers you want to support work with CSS variables, then all you have to do is add this:

$css-vars-use-native: true;

Instead of aligning the variable properties in the resulting CSS, the mixin will start registering custom properties, and the var() instances will go to the resulting CSS without any transformations. This means you’ll have fully switched to CSS custom properties and will have all of the advantages we discussed.

If you want to turn on the useful debugging information, add the following:

$css-vars-debug-log: true;

This will give you:

  • a log when a variable was not assigned but was used;
  • a log when a variable is reassigned;
  • information when a variable is not defined but a default value gets passed that is used instead.

Conclusion Link

Now you know more about CSS custom properties, including their syntax, their advantages, good usage examples and how to interact with them from JavaScript.

You have learned how to detect whether they are supported, how they are different from CSS preprocessor variables, and how to start using native CSS variables until they are supported across browsers.

This is the right time to start using CSS custom properties and to prepare for their native support in browsers.

(rb, vf, al, il)

Footnotes Link

  1. 1 https://www.smashingmagazine.com/2014/03/introduction-to-custom-elements/
  2. 2 https://www.smashingmagazine.com/2016/03/houdini-maybe-the-most-exciting-development-in-css-youve-never-heard-of/
  3. 3 https://www.smashingmagazine.com/2016/02/everything-about-google-accelerated-mobile-pages/
  4. 4 https://www.smashingmagazine.com/2016/05/better-architecture-for-ios-apps-model-view-controller-pattern/
  5. 5 http://www.xanthir.com/blog/b4KT0
  6. 6 https://developer.mozilla.org/en-US/docs/Web/CSS/:root
  7. 7 https://www.smashingmagazine.com/wp-content/uploads/2017/03/closure-large-opt.png
  8. 8 https://www.smashingmagazine.com/wp-content/uploads/2017/03/closure-large-opt.png
  9. 9 https://www.smashingmagazine.com/wp-content/uploads/2017/03/closure-scss-large-opt.png
  10. 10 https://www.smashingmagazine.com/wp-content/uploads/2017/03/closure-scss-large-opt.png
  11. 11 'http://codepen.io/malyw/pen/MJmebz/'
  12. 12 'http://codepen.io/malyw'
  13. 13 'http://codepen.io'
  14. 14 'http://codepen.io/malyw/pen/bgWerv/'
  15. 15 'http://codepen.io/malyw'
  16. 16 'http://codepen.io'
  17. 17 'http://codepen.io/malyw/pen/WRjxOy/'
  18. 18 'http://codepen.io/malyw'
  19. 19 'http://codepen.io'
  20. 20 'http://codepen.io/malyw/pen/ggWMvG/'
  21. 21 'http://codepen.io/malyw'
  22. 22 'http://codepen.io'
  23. 23 'http://codepen.io/malyw/pen/PWmzQO/'
  24. 24 'http://codepen.io/malyw'
  25. 25 'http://codepen.io'
  26. 26 https://developer.mozilla.org/en/docs/Web/CSS/all
  27. 27 https://drafts.csswg.org/css-variables/#defining-variables
  28. 28 https://github.com/w3c/webcomponents/issues/300#issuecomment-144551648
  29. 29 'http://codepen.io/malyw/pen/KzZXRq/'
  30. 30 'http://codepen.io/malyw'
  31. 31 'http://codepen.io'
  32. 32 https://codepen.io/malyw/pen/XpRjNK
  33. 33 https://codepen.io/malyw/pen/EZmgmZ
  34. 34 'http://codepen.io/malyw/pen/dNWpRd/'
  35. 35 'http://codepen.io/malyw'
  36. 36 'http://codepen.io'
  37. 37 https://blog.hospodarets.com/passing_data_from_sass_to_js
  38. 38 https://developer.mozilla.org/en/docs/Web/API/Window/matchMedia
  39. 39 'http://codepen.io/malyw/pen/xgdEQp/'
  40. 40 'http://codepen.io/malyw'
  41. 41 'http://codepen.io'
  42. 42 https://codepen.io/malyw/pen/oBWMOY
  43. 43 http://caniuse.com/#feat=css-variables
  44. 44 https://www.smashingmagazine.com/wp-content/uploads/2017/04/css-variables-large-opt.png
  45. 45 https://www.smashingmagazine.com/wp-content/uploads/2017/04/css-variables-large-opt.png
  46. 46 https://developer.mozilla.org/en/docs/Web/CSS/@supports
  47. 47 https://ashleynolan.co.uk/blog/frontend-tooling-survey-2016-results
  48. 48 https://github.com/postcss/postcss-custom-properties
  49. 49 https://github.com/malyw/css-vars
  50. 50 http://cssnext.io/
  51. 51 https://github.com/malyw/css-vars

↑ Back to top Tweet itShare on Facebook

Serg is a Lead Front-End Developer / Engineering Manager based in Dublin. He loves the Web and shares his knowledge in his blog. Being a big fan of moving CSS and JavaScript forward, Serg participates specifications debates and discusses new features with the community. He likes to stay on top of the latest Web technologies and browser additions, participates their development, standardization, and enhancement.

  1. 1

    All good and well, but what’s the point? Can we have a real world example of when this will actually be useful to somebody?

    6
    • 2

      Check out this

      3
    • 3

      Being able to share variable data between CSS and JS is really great, especially for a lot of responsive work I’ve been doing for the last few years.

      2
    • 4

      Jon Hobbs-Smith

      April 22, 2017 8:57 pm

      I think if you don’t know then maybe it’s not for you, but seriously, you can’t have used CSS much if you’ve never found anything that would be made easier using variables.

      0
    • 5

      OK, so I have a specific color scheme and need to apply the same color value (say “#b4d455”) to 200 selector/property combinations. Do I just type that value directly for each of the 200 property declarations or do I declare it once as a variable and refer to it by some self-descriptive name 200 times. When I come to read the stylesheet 2 years later, which approach conveys the original intention more clearly and which is easier to change?

      1
  2. 6

    Paolo Cargnin

    April 19, 2017 5:13 pm

    I think that’s awesome. Really, really awesome!

    But…Preprocessors are way more easy to use (maybe it’s just my initial opinion), and half of the things can already be done with standard css and classes and this change only the sintax.

    0
    • 7

      The difference is similar somehow to bundlers/compilers and native JavaScript- something just cannot be achieved without native support in the browser, like Proxy etc.
      CSS Custom Properties are alive and you can think about them as Sass, which is recompiled and reapplied immediately by the browser on the fly.
      You may be correct saying “half of the things can already be done..”, but at least there is the second half 😉

      11
    • 8

      Let’s see, one is a real standard browsers can read. Another requires extra programs to compile in a quasi-language that browsers will never understand…

      Is this even up for debate?

      3
      • 9

        I’m a little confused by your comment. Are you in favour of preprocessors? Or are you stating that the use of CSS custom properties is a no-brainer?

        -5
  3. 10

    Sander Hagenaars

    April 19, 2017 5:36 pm

    I dont get it. With preproccessors you dont get variables etc cluttering the final stylesheet. With this it just creates a large css file. Am I missing something?

    -6
  4. 12

    I guess custom properties allows us to declare variables in the :root element which can be used through out or other elements. This is good for small projects that don’t need a pre-processor. The only bummer for me is that its still experimental technology and IE11 doesnt support it.

    1
  5. 15

    Very good article. CSS variables are definitely very useful, you can use them dynamically. As the article says “…are opening new horizons for web development”.
    I’ve found myself in a position to use them, but I gave up at that time because of browser support.
    Just think about combining them with twig and sass ;).

    6
  6. 17

    Are the CSS Preprocessor a standard today on web development? Really? Don’t think so. The real standard is CSS, everything else is an external tool.
    CSS has a simple syntax. Don’t know why everyone want complex it.

    3
    • 18

      There are many opinions regarding that.
      I cannot disagree, that preprocessors moved CSS and the community forward, providing ideas and implementations.
      On the other hand, as result, it’s always pure CSS, which today gets better and better.
      You can find my representation of the community move to preprocessors, and currently leaning towards pure CSS and JS when possible in my presentation You might not need a
      CSS preprocessor

      5
    • 19

      Preprocessors add another layer of abstraction. The web development wizards are not content with the limitations provided with writing pure CSS, they want to do dynamic language acrobatics with it, that is why they invented preprocessors to do just that. I use SASS sparingly, only to declare variables, and prefixes, but for the most part I stay away from advanced CSS equations.

      0
  7. 20

    Alex Batista

    April 19, 2017 6:57 pm

    Personally, I prefer to code on Sass. It’s simplest than that.

    -9
  8. 21

    Great article! I’m very excited about css variables. To answer some comments on here, I recently wrote about my real world use case for CSS variables, and how I integrated them with React. https://medium.com/geckoboard-under-the-hood/how-we-made-our-product-more-personalized-with-css-variables-and-react-b29298fde608

    3
    • 22

      Hi, Dan, thanks. I saw your article- nice write-up.
      There are other interesting solutions to use CSS Custom Properties with React, e.g. https://github.com/jide/react-css-variables in case you use CSS in JS and styled-components.

      1
    • 23

      I was really excited for a while (and immediately thought of theming like Dan has implemented) before I realised we now need native colour functions to be able to do proper deep theming (where a certain colour could be based on your base theme colour but more saturated, slightly off hue, inverted, etc) like Twitter have done a bit. Custom properties will open doors for sure but not quite enough of them :).

      Thanks for the posts, two of you, good stuff.

      0
  9. 24

    Brady Sterling

    April 19, 2017 11:51 pm

    I used custom properties in the global scope of a angular project, but the properties didn’t render on mobile browsers running on iOS :(

    1
  10. 25

    I am excited about this technology but… I am curious about your highlighted class and default class example. Could you explain why the technique used in that example would be useful?

    1
  11. 26

    Why is it better than:

    .highlighted {
    font-size: 30px;
    }

    .default {
    font-size: 10px;
    }

    3
    • 27

      In short- it’s less code and CSS output when you want to override only the variable values for specific classes/states.

      SCSS example:

      $font-size: 10px;
      .default {
      font-size: $font-size;
      }
      .highlighted {
      $font-size: 30px;
      font-size: $font-size; // copy-paste + more CSS output for ".highlighted"
      }

      CSS example:

      :root{
      --font-size: 10px;
      }
      .default {
      font-size: var(--font-size);
      }
      .highlighted {
      --font-size: 30px;
      }

      Now imagine when you have some button states or theming when you need to overwrite many values and apply different styles just from new values.
      E.g. check this demo.

      0
  12. 28

    Yet another solution to a problem that doesn’t exist. Why do you want to complicate things so much and destroy css by turning it into something else. Just use css and use it well, you don’t need all this shit in your life.

    -16
  13. 29

    “CSS custom properties are supported in all major browsers:”

    You then show it is directly not supported in IE11. Many real life scenarios require at least the latest IE (11) or even back further.

    30
  14. 30

    “CSS custom properties are supported in all major browsers:”

    Except IE11 and Android. Even with Edge – only just, and most users are still on an old version that doesn’t support this yet.

    Great article, but we need to be more responsible about encouraging developers to use features that are not yet fully supported.

    17
    • 31

      Exactly. That was the first thing I was searching for: “internet explorer” (0 results) and “polyfill (0 results) when starting to read the article.

      cu, w0lf.

      0
    • 32

      In the first place, we should be more responsible to users with disabilities, which are much larger percentage than the percentage of users ie11.

      2
  15. 33

    One important disadvantage of the custom properties that has to be mentioned is that you can’t use modifiers with this standard as usual. When you try to add an special style in an “active” class like this.


    .link { --link-color: red; }
    .link.link--active { --link-color:blue: }

    The browser can only have one value per custom property, so the –link-color property can’t have different values for different css selectors. By the way, nice article, i agree with the idea that the preprocessors won’t be necessary in a lot of developments :)

    0
  16. 35

    Wim Mostmans

    April 20, 2017 4:35 pm

    Excellent article!

    There is a typo in the example where you define –gap, this should be –spacer

    0
  17. 37

    These are _variables_. _Properties_ is already another thing in CSS (`width`, `box-sizing`, etc).

    -4
  18. 39

    David De Coninck

    April 21, 2017 12:53 pm

    Great article.

    Seems like window.css.supports('--a', 0) returns false in Safari 10.1

    Instead use window.css.supports('color', 'var(--a)')

    0
  19. 41

    This is amazing! Hurry up browsers, we need this for all of you!

    0
  20. 42

    Eric Johnson

    April 21, 2017 4:21 pm

    FYI, revert is not widely supported yet.
    http://caniuse.com/#feat=css-revert-value

    1
  21. 43

    Start using them to get a feel for how they work? Definitely. They’re great, they can reduce your code ***a lot*** if you’re smart about using them.

    Actually use them to create something cool that should work cross-browser? Well, no, sorry, can’t do that yet. And the problem isn’t with the support for custom properties. The problem is with `calc()` support.

    In the most basic case, `calc()` has been working in all browsers for years. However, except a few particular cases*, it only works where you’re sure to only use length values. You cannot use `calc()` inside a `rotate()`, a `scale()`, as an `animation-delay` value, as a `stroke-width` and so on…

    What does that have to do with CSS variables? Well, a lot. These support limitations for `calc()` didn’t matter back when using CSS variables was still out of the question. But now they do. I’ve played with CSS variables a lot for the past couple of months and I’ve discovered it’s impossible to take full advantage of what they have to offer as long as `calc()` doesn’t have better support as a non-length value.

    Now if I want to do a cross-browser, minimal pure CSS cube using custom properties… well, I either have to give up the idea of it actually being minimal or the idea of it being cross-browser (demo).

    * For example, Edge 15 supports `calc()` inside `rgb()`/ `hsl()` functions and Firefox has supported calc() as a value for line-height for quite a while.

    6
  22. 45

    I’m not a developper, but I’m teaching HTML/CSS to communication and art students. Reading your article, I asked myself “why” everytime. I understand that preprocessors can help in great big project, and maybe CSS custom properties too. But 1) preprocessors produce *in fine* pure CSS, I don’t see that it’s the case for custom properties, and 2) for *the rest of us*, I really don’t understand the gain to write variable to store… colors; that I can just write (or store in a snippet to remember it). Why do I have to charge in mind custom properties for things I can remember by its name?
    Really, I DON’T understand.
    Why, finally, do you want to transform CSS, which is a **description** language, to a programmation language that it will never be (except if someone invent a new description language to replace it?), because it has not made with this goal?

    3
    • 46

      I do understand the approach. Just take a look at the bootstrap CSS code. As a whole. Now go on with “lets replace those colors by hand” or via search & replace. Yep, lots of effort. Esp. when you want to separate specific blocks for specific topics (eg. forms, tables, lists etc). Then this turns into “directory-wide search & replace” – even worse. Next on, what happens if you want to replace all but a few specific elements / selectors from the search & replace orgy? Let’s learn regular expressions, just for that? Thats a huge amount of effort just to stay away from “turning CSS into a programming language”.

      Personally, I like the way LESS and similar preprocessing systems work. You get nice variables, and assign them to all elements that require them, and all the elements you want to specifically have, to take on my example, a specific color, then yes, let them have it.

      Of corpse, this much effort is a total waste of time if your CSS code consists of like .. a bunch of lines, up to 100 or so. But with 10k of lines – not so much (practical example in use in an existing project thats bound to go live in a few days).

      cu, w0lf.

      0
  23. 47

    mariuszdaniel

    April 21, 2017 10:13 pm

    You can also overwrite css variable within media query something like this:

    :root{
    --indent-size: 10px;

    --indent-xl: calc(2*var(--indent-size));
    --indent-l: calc(var(--indent-size) + 2px);
    --indent-s: calc(var(--indent-size) - 2px);
    --indent-xs: calc(var(--indent-size)/2);
    }
    @media (min-width: 480px) {
    :root {
    --indent-size: 20px;
    }
    }

    @media (min-width: 960px) {
    :root {
    --indent-size: 40px;
    }
    }

    Working example on codepen (resize window)

    -1
  24. 48

    Diego Mendonça

    April 24, 2017 1:05 pm

    Great article!
    Just started to use the discussed technique and I’m glad to see the results.

    0
  25. 49

    this example http://codepen.io/malyw/pen/xgdEQp doesn’t seem to work on firefox (v52, windows 10). Any idea why?

    0
  26. 52

    Just started using CSS custom properties and found this article. Thanks!

    1
  27. 53

    Finally! As a webdev that likes to keep sites slim and lightweight, having a single variable sprinkled throughout a stylesheet saves a lot of time!

    1
  28. 54

    Cary Hartline

    April 27, 2017 12:03 am

    I was very much on board with everything and I was ready to dump SCSS out the window, but the lack of support in Opera Mini and Internet Explorer is a very big deal for CSS that handles the basic structure of a webpage. This would be a lot easier if this new feature was a whiz-bang animation, but to add in a lot of backwards-looking CSS and to rewrite current CSS is a bit too much.

    1
  29. 55

    Vikash Agarwal

    April 27, 2017 5:23 pm

    How about loop?

    -1
  30. 56

    That’s awesome explain the proper points of CSS. Really great!
    CSS is very useful markup language. You can control over the appearance of a web page to the page creator. I also make a blog with HTML and CSS. Its simplest than other.
    Thanks for the article. I have learned many things in that topic.

    0
  31. 57

    Gene Biondo

    May 2, 2017 4:43 pm

    Very nice. Can’t wait until IE11 drops out of my business support.

    The above @supports(not) actually does not work at all in IE11. Adding this -ms line expands it to cover IE10-11:

    @media all and (-ms-high-contrast: none), (-ms-high-contrast: active),
    @supports ( not (--a: 0)) {
    /* IE10-11, or cust props not supported */
    body:after {
    content: '--screen-category : NOT SUPPORTED in this browser ';
    }
    }

    0

Leave a Comment

You may use simple HTML to add links or lists to your comment. Also, use <pre><code class="language-*">...</code></pre> to mark up code snippets. We support -js, -markup and -css for comments.

↑ Back to top