By writing valid CSS and using custom properties you can future-proof your code; here's how to get started.

Custom properties (or CSS variables) are not a new feature, they are in the CSS spec since 2014, but browser vendors lagged behind. Firefox adopted early, Safari and Chrome in 2016, and in late 2017 even Edge implemented it fully, giving it 88% global coverage with IE and Blackberry’s browser pulling back the number. In 2018 it is safe to say that it’s ready for the modern web.

Choosing the native implementation over a rigid processor gives you much more than a simpler development process. Custom properties are live, they can be changed based on media queries, element scope or with plain javascript. When a CSS variable changes, the browser repaints the already rendered view without any additional push. It’s low-level and fast.

The ability to iterate and change things without unnecessary layers of abstractions enables you to ship faster and build a better product.


One of the single best feature of CSS processors is variables. Having the ability to declare once and reuse the variable across your project should not be optional in any system. Using a pre-processor like SASS or LESS gives you at least one extra build step, which can be a pain if you just want to build a thing fast. CSS has come a long way since the dark days of table hacks, let’s look into one of the most useful one: Custom Properties.

We'll start with a simple example.

:root {
  --color-red: #fc4752;
}

.site-navigation {
  background-color: var(--color-red);
}

.site-footer {
  color: var(--color-red);
}

The :root pseudo selector targets the highest-level parent element in the DOM, giving all elements access to --color-red. Yes, CSS variables definition should start with --, and you can access them anywhere with var(). Well, anywhere, that is cascaded under our :root selector.

With var() you can also define a fallback value, which will be used if the given variable is unreachable or non-existent for the class.

.site-navigation {
  background-color: var(--color-red, red);
}

Values are inherited from the DOM, which means you can make them more specific.

:root {
  --color: red;
}

.site-navigation {
  --color: green;

  background-color: var(--color);
}

.site-footer {
  color: var(--color);
}

Every var(--color) is red, except every var(--color) under site-navigation. Generally speaking it’s not a good practice to overwrite an already defined value, but there are cases when a scoped value is the most adequate solution.

These are the basics, but you can do so much more. Another great example is changing layout based on viewport size.

:root {
  --color-red: #fc4752;
  --flex-layout: row;
}

@media (max-width: 640px) {
  :root {
    --flex-layout: column;
  }
}

.site-navigation {
  display: flex;
  flex-direction: var(--flex-layout, row);

  background-color: var(--color-red, red);
}

.site-footer {
  color: var(--color-red);
}

Under 640px the media query triggers, changing the flex direction to column on site-navigation class, making it’s content vertical (column) instead of the default horizontal (row). In this example, because of the default variable given to site-navigation, you don’t even need the initial :root definition of --flex-layout, it will go straight to row.

This is cool, but not all fun and games just yet. For instance, since the media query is not an element, the breakpoint value cannot come from a custom property. Although CSS Working Group have a draft of using env() for queries, vendor implementation and proper support is probably years from today.

That’s ok. We will stick to what we have now. One of the more advanced use case for custom properties is switching themes. You can define a base theme, build your website around it, and just switch it out with the browser doing the heavy lifting. And it’s not even heavy.

Think about Twitter night mode, but without actually switching the CSS (I see you).

<main>
  <nav class="site-navigation">
    <div>I love interwebs</div>
  </nav>
  <div class="site-footer">
    <label for="color-picker">Change color</label>
    <input type="color" id="color-picker">
  </div>
  <style>
    :root {
      --color: #fc4752;
    }

    body { margin: 0; padding: 0; }

    main {
      display: flex;
      flex-direction: column;
      justify-content: space-between;
      height: 100vh;
    }

    .site-navigation,
    .site-footer {
      padding: 12px;
    }

    .site-navigation {
      background-color: var(--color, red);
    }

    .site-footer {
      color: var(--color);
    }
  </style>
  <script>
    var colorPicker = document.querySelector("#color-picker");

    colorPicker.addEventListener("change", function() {
      document.documentElement.style.setProperty("--color", this.value);
  })
  </script>
</main>

Input color’s picked value replaces the document’s --color value, making the change without a hitch. You can play with blend modes, alpha channel colors or pngs, the possibilities are endless and fun.

Now, with your future proof CSS in place, which depends on no third party developer, using only custom properties there is a good chance you are covered and even good to go production.

But what if you want something other than variables in your code, let's say CSS modules maybe? As of today, for existing CSS features like modules or nesting, you couldn’t spare the extra build step any longer, but you can implement it with the sweet promise of not having to rewrite code when vendors catch up with the spec.

Instead of refactoring your CSS every time you want to improve your output, you should write your code in a specification aligned way in the first place. This is the main difference between pre- and post-processors. A pre-processor actually writes the CSS for you (basically, from a text file), while a post-processor aligns your already valid CSS for more browser support, latter giving you more flexibility in the process.

Using the native method always beats the workaround, and having the working knowledge of future technologies is the best bet you can have going forward learning CSS.


This article was also published under Net Magazine / issue 306.