Skip to content Paweł Grzybek

CSS Custom Properties explained

In programming languages the term “variable” describes a storage location normally associated with an identifier that contains some value. Despite fact that CSS is a markup language, spec creators were very generous recently and gave us a tiny, but very powerful piece of real programming capability. Excitement about native CSS Custom Properties is generally tempered by the incorrect comparison to variables used in preprocessors like Sass or LESS. Don’t be fooled by this misconception! Bare with me for the rest of this article and let’s embrace the power of this new native feature together.

Syntax

When I saw the syntax for a first time I wasn’t a big fan of it. To be honest, not much has changed since. One of the spec creators gave us a fair explanation behind the naming decisions.

If we use $foo for variables, we’ll be unable to use it for future “variable-like” things.

A declaration can be made on any selector and it requires a valid identifier that starts with two dashes. Unlike other CSS properties, variable names are case-sensitive. They follow inheritance and specificity rules like all other ordinary properties. If you need a reminder on how specificity in CSS works, I encourage you to read “CSS Specificity explained” first. The common practice is to declare it on the :root element to keep it accessible in all child selectors.

:root {
  --brand-color: #06c;
}

To reference a variable value use the var() function.

h1 {
  color: var(--brand-color);
}

Custom properties can share values between each other.

:root {
  --brand-color: #06c;
  --logo-color: var(--brand-color);
}

The new var function allows a fallback value in case the property wasn’t declared beforehand.

p {
  font-family: var(--font-stack, Roboto, Helvetica);
}

// Yes, quotation marks are not needed
// More about that here
// https://mathiasbynens.be/notes/unquoted-font-family

Variables cannot be a property name or part of a value. The following examples are not valid.

.foo {
  --side: margin-top;
  var(--side): 20px;
}
.foo {
  --gap: 20;
  margin-top: var(--gap)px;
}

// Use calc() instead
.foo {
  --gap: 20;
  margin-top: calc(var(--gap) * 1px);
}

The difference between native CSS Custom Properties and Sass variables

Preprocessors like Sass or LESS are fantastic! The variables available in these tools are not exactly the same as CSS Custom Properties though. Further, the variables that are available in the popular PostCSS plugin called cssnext use the same syntax as native CSS Custom Properties but still behave exactly the same as those from Sass or LESS. They apply a static value to the declaration during the compilation process. The new native CSS feature applies a value to a DOM element on runtime meaning that the browser allows us to reassign it. The dynamic nature of this feature allows us to take advantage of other language facilities like media queries or cascading. Let’s have a look at the classic example that is impossible with preprocessors, but works fine with native CSS.

$fz: 1rem;

@media (min-width: 60rem) {
  $fz: 1.5rem;
}

body {
  font-size: $fz;
}

The above example applies a static font-size: 1rem to the body tag. The media query is completely ignored because a media query is not something that works in the C or Ruby compiler, it is something that works only in the browser. To make it work in Sass we have to create two separate variables and assign them depending of the breakpoint. CSS Custom Properties don’t require the additional compilation process so the browser applies the value at runtime. When we change the width of the viewport, the value of the property is reassigned as expected.

:root {
  --fz: 1rem;
}

@media (min-width: 60rem) {
  :root {
    --fz: 1.5rem;
  }
}

body {
  font-size: var(--fz);
}

In my opinion it is freaking awesome and it opens an array of new options that weren’t possible before. I have many more examples in my head, but hopefully this one clearly illustrates the subject.

Working with CSS Custom Properties and JavaScript

The true power of CSS Custom Properties comes when we combine it together with JavaScript. It takes very few lines of code to allow us to get and change the value of CSS custom property. Let’s go through a few simple examples.

Get the value of CSS Custom Properties

To get a value of a property we have to use getPropertyValue().

:root {
  --brand-color: salmon;
}
var styles = getComputedStyle(document.documentElement);
var customProp = String(styles.getPropertyValue('--brand-color')).trim();

console.log(customProp);
// salmon

Reassign the value of CSS Custom Properties

To reassign the value setProperty() comes handy.

:root {
  --brand-color: salmon;
}

p {
  color: var(--brand-color);
}
document.documentElement.style.setProperty('--brand-color', 'purple');
<p>I’m a purple paragraph!</p>

Detect the browser support for CSS Custom Properties

Browser support for CSS Variables isn’t great at the time of writing this article. Google Chrome 49+, Firefox 31+, Safari 9.1+ and iOS 9.3+ doesn’t make it reliable enough to use it in production. Luckily we have a few methods to detect the hero of today’s article. Native feature detection with CSS.supports() API helps us with that. Lets have a look at how to use it in CSS and JavaScript.

body {
  --bg-color: #e46764;
  background-color: pink;
}

@supports (background-color: var(--bg-color)) {
  body {
    background-color: var(--bg-color);
  }
}
CSS.supports('--bg-color', '#e46764');
// returns a boolean value

This method isn’t bulletproof because support for @support isn’t amazing at all. Wes Bos posted a much more reliable method the other day. Thanks man!

function testCSSVariables() {
  // create a new element
  var el = document.createElement('span');

  // add a new custom property and apply it as a background
  el.style.setProperty('--color', 'rgb(255, 198, 0)');
  el.style.setProperty('background', 'var(--color)');

  // print new element to the DOM
  document.body.appendChild(el);

  // assign computed styles of element
  var styles = getComputedStyle(el);

  // return true if background color is assigned correctlly, false otherwise
  var doesSupport = styles.backgroundColor === 'rgb(255, 198, 0)';

  // remove element from the DOM
  document.body.removeChild(el);

  // return boolean value
  return doesSupport;
}

testCSSVariables();
// returns a boolean value

Day / night mode switch

Codepen is already full of beautiful projects that use CSS Custom Properties. Mine is nothing near this level of creativity but I had good fun doing it. Before checking example below, please make sure your browser supports CSS Custom Properties first. I’m super curious about how you use CSS Custom Properties. Don’t be shy and post a link in the comments section :)

See the Pen 2016-02-19-css-custom-properties-explained by Pawel Grzybek (@pawelgrzybek) on CodePen.

Comments

  • l
    luka G.

    would be super nice if these variables were controllable with the use of pseudo-classes

    :checked, :hover

    etc.

    👆 you can use Markdown here

    Your comment is awaiting moderation. Thanks!
    • L
      Lu Nelson

      They are.


      .foo {
      background-color: var(--color);
      --color: blue;
      }
      .foo:hover {
      --color: red;
      }

      👆 you can use Markdown here

      Your comment is awaiting moderation. Thanks!
      • l
        luka G.

        Cool, U are right!
        For some reason it doesn't work in JSFiddle when it is set to .scss
        but in .css it works just fine : )

        https://jsfiddle.net/z8f3vj...

        In "Day / night mode switch" JavaScript could be omitted then : D

        👆 you can use Markdown here

        Your comment is awaiting moderation. Thanks!
        • Pawel Grzybek
          Pawel Grzybek

          Yeah, your example is cool!

          Custom properties are scoped to selector, so as long as something is in the scope, it's fine to modify. I used JavaScript on purpose. I wanted to use in one simple example all the techniques that I mentioned on the blog post.

          👆 you can use Markdown here

          Your comment is awaiting moderation. Thanks!
        • Pawel Grzybek
          Pawel Grzybek

          It's strange that it doesn't work with .scss. Sass compiler skips all the var declarations as expected. I'm almost sure it is just a jsfiddle specific behavior, because I can't see any reason why it shouldn't work.

          👆 you can use Markdown here

          Your comment is awaiting moderation. Thanks!
  • a
    asinnema

    Hi Pawel, thanks for your article! I don't really comment on blogs very often but I just wanted to say I appreciated you saying "Mine is nothing near this level of creativity but I had good fun doing it." - a level of modesty is what I find is sometimes sorely lacking in our industry. Good on you for setting a positive example!

    👆 you can use Markdown here

    Your comment is awaiting moderation. Thanks!
    • Pawel Grzybek
      Pawel Grzybek

      Ha :) Thanks for nice input @asinnema:disqus. Thanks for visiting my website and have a great day!

      👆 you can use Markdown here

      Your comment is awaiting moderation. Thanks!
  • W
    We Buy Houses

    as per it's abbreviation CSS which stand for central superior services, I have found it superior and fantastic.

    👆 you can use Markdown here

    Your comment is awaiting moderation. Thanks!
  • D
    Dmitri

    Awesome article and great example implementation, love the simplicity and absence of bloat.
    Nice trick pushing 50% and translating back.

    Here is a slight simplification with fewer transforms and some more variable to control the label width inside calc:
    https://codepen.io/dmitriz/...

    BTW, you can simplify `if(mode.checked === true)` to `if(mode.checked)`.

    👆 you can use Markdown here

    Your comment is awaiting moderation. Thanks!
    • Pawel Grzybek
      Pawel Grzybek

      Nice nice nice! Thanks for kind words about post. Thanks for hints about simplification. Have a great day.

      👆 you can use Markdown here

      Your comment is awaiting moderation. Thanks!

Leave a comment

👆 you can use Markdown here

Your comment is awaiting moderation. Thanks!