You Should Really Be Using CSS Variables

Last updated
  • best practice
  • css

It’s almost a law of modern web development: At the beginning of each project, there’s a heated debate about stylesheets. Should we go with LESS, Sass, or even Tailwind CSS or Bootstrap?

In my opinion, the optimal solution is something entirely different: vanilla CSS! The advantage is huge: You do not need to preprocess or compile plain CSS. This speeds up development and simplifies dependency management and build pipelines. You cannot run into frustrating specificity issues caused by non-deterministic build results - don’t we all hate those?

In addition, you gain flexibility; you can use CSS anywhere and anytime. You do not rely on community plugins that might get deprecated. This is especially relevant if your team is relatively small and might not have the resources to migrate to another preprocessor should the need arise.

Last but not least, CSS provides you with an additional way to encapsulate style-related data, which allows for cleaner source code structures. I will explain this a bit further down.

»But,« you might object, »what about all the perks we lose? What about themes and configuration variables? And isn’t CSS much more complicated to write?«  Those points aren’t valid anymore; CSS developer experience has improved a lot. All major browsers now support variables (also known as custom properties), so you can create CSS-only themes easily. However, if you are unlucky and still need to support the Internet Explorer, I’d advise you to keep using a framework for now.

CSS Variables vs. LESS/Sass

In my opinion, the introduction of CSS variables has been a game-changer. You can use them as follows:

:root {
  --border-radius: 1rem;
}

.card {
  border-radius: var(--border-radius);
}

Here’s the same code written in LESS for comparison:

@borderRadius: 1rem;

.card {
  border-radius: @borderRadius;
}

Naturally, you can also access variables in functions, just like you could use LESS or Sass variables:

.card {
  --border-radius: 1rem;
  --hsl: 322, 100%, 53.9%;

  border-radius: calc(var(--border-radius) / 2); /* 0.5rem; */
  color: hsla(var(--hsl), 0.5); /* hsla(322, 100%, 53.9%, 0.5) */
}

As you can see, the difference is primarily cosmetic. But keep in mind that CSS variables do not have to be compiled! This simplifies development.

CSS Themes vs. Tailwind/Bootstrap

CSS variables allow you to configure default styles and create advanced themes like Open Props. While classic CSS frameworks like Tailwind or Bootstrap use helper classes, CSS themes use helper properties. This is a lot cleaner when you want to override styles manually (and let’s be honest, you always want to do that at some point):

<p class="custom-warning">Some text</p>
<style>
  .custom-warning {
    /* All styles in one place */
    margin: var(--m-1) var(--m-2) 1rem;
    font-weight: var(--semibold);
    color: red;
  }
</style>

As a quick reminder, the same solution would contain an intransparent mixture of raw CSS and helper classes in Tailwind or Bootstrap:

<p class="m-2 mt-1 font-semibold custom-warning">Some text</p>
<style>
  .custom-warning {
    /* You might even need to tweak specificity for this one to work. */
    margin-bottom: 1rem;
    color: red;
  }
</style>

Isn’t the first version much nicer?

However, there’s one caveat: Apart from helper classes, Bootstrap also provides helpful JavaScript snippets that a CSS theme cannot replace. Usually, this won’t be a problem, but you still have to keep it in mind.

CSS Variables Keep Your Code Clean

I’ve told you before that CSS variables are almost the same as LESS/Sass variables. That was a lie. CSS variables are much more powerful. As the name »custom properties« suggests, CSS variables behave like other CSS properties, which means that they get inherited by children. They can also be reassigned as desired. Have a look at the following button group demonstrating these principles:

<section style="--accent: blue">
  <button>Do something</button>
  <button>Do something else</button>
  <button class="danger">Do something dangerous</button>
</section>

<style>
  button {
    /* inherited from parent section - blue */
    background: var(--accent);
  }
  .danger {
    /* override - the third button will be red */
    --accent: red;
  }
</style>

In other words, you can use CSS variables to encapsulate style information along with the DOM - independent from component architecture. The possibilities are sheer endless: Need to know the current background color so you can display some effect correctly? Use and update--bg-color accordingly. Struggle with passing style data between various components? Encode it in variables. Want to reuse a component with a different color theme? Again, use variables. There are so many use cases I might even write a whole blog post about them!

Unnoticed by many, vanilla CSS has become a hot contender for the title »best way to style web apps.« Let’s get back to the basics - CSS is ready.