Playing around with CSS Variables

Since the last time I looked, CSS added support for variables and it's nice for keeping colors consistent.

Posted by Tejus Parikh on October 9, 2023

I keep finding myself re-remembering that everything is a moving target. It’s been a very long time since I’ve worked with straight CSS, only using it abstracted out through SASS or React. So I’ve missed most of the major enhancements to the spec and there’s much that can be done without a framework now.

One I just used was CSS Variables. I remember being extremely frustrated that CSS didn’t have variable support and doing all sorts of hacky thing to templatize my CSS, before turning to LESS and SCSS.

More recently, I was working in a pared-down environment and started to get really tired of copy-and-pasting the key theme colors. It also got annoying when I decided to edit to the scheme a little and had to remember which hex value was actually the one I wanted. This would be about the time I would have to burn the time to add a CSS processor to the build pipeline. So I was thrilled to find out that I didn’t have to.

CSS variables have been pretty available since 2016 and though the syntax is somewhat clunky, they work.

Defining a CSS variable

You can define a variable within a context. So if you wanted to define a variable that would only be available to elements with a specific class, you would do:

.my_special_class {
  --text-color: #42403A;
}

Variable names are differenciated from property names with the -- prefix. I feel this is a little awkward looking, but it’s also pretty conflict free in the broader CSS ecosystem.

You can also define CSS variables globally with the special :root signifier.

:root {
  --text-color: #42403A; /* Available to all elements */
}

Using a CSS variable

You can reference the variable with the var() function. var() also takes an optional second parameter to be the default.

.my_cool_component {
  color: var(--text-color);
  /* `--bg-color` is not defined, so the default #f1f1f1 will be used */
  background-color: var(--bg-color, #f1f1f1); 

var() is not semantically aware, nor does it do anything special. You can essentially think of it as doing a straight string replacement in the location it is specified. CSS variables can also only been used in property values. In other places it will be invalid syntax and ignored.

Utilizing the above for easier color management

A common need in this particular project was to use colors with varying opacities. (Clearly this project is design by developer). Since var() is kinda text replacement, but also kinda not, so you can’t do color: var(--text-color)88;. What does work is specifying a color triple and using the rgb() and rgba() functions.

So if I wanted to be able to control the alpha channel, I’d have to write my css vars like the following.

:root {
  --main-color: 66,64,58;
}

.text {
  color: rgb(var(--main-color)); /* Use the color without an alpha channel */
  border-color: rgba(var(--main-color, 0.8)); /* Add an alpha channel */
}

Why bother?

There are ways of accomplishing this goal, with arguably cleaner syntax, in the common tooling that would accompany a component framework. In my case, most of the CSS was trivial and this was the only template like feature I needed. Adding the overhead of more tooling was overkill.

For larger projects, there is inevitably going to be disagreements about which framework is best. CSS variables allow you to separate your branding from developer preferences and keep the user experience more cohesive. I’ve been through enough framework upgrades and enhancements to find pushing stuff into the native layers useful. It’s one less thing to worry about when making a change.

Original image is CC-licensed [original source]

Tejus Parikh

I'm a software engineer that writes occasionally about building software, software culture, and tech adjacent hobbies. If you want to get in touch, send me an email at [my_first_name]@tejusparikh.com.