How to improve your styling workflow with CSS variables

This post is also available in: Español (Spanish)

Intro to variables. What are they?

If you’ve ever worked with code before you should be familiar with the concept of variables. We could imagine them as named boxes where we can store data. We can then access, retrieve or replace the content of those boxes. Variables are core elements in any programming language, but not in markup languages −like HTML5− as these are just instructions to be interpreted, and do not usually compute or calculate things. 

Variables are like boxes where you store different kinds of data

The same tends to apply to CSS, as a declarative language, being mostly considered as a way to make a site pretty by separating the visual part from the content structure. That is why CSS preprocessors (such as LESS and SASS) became popular: they make writing CSS much easier by letting us play with variables, functions and work on a more readable nesting system. 

CSS preprocessors are awesome for large projects but may be a bit overkill when you just need some quick styling for a landing page, or a new visual theme to play around with in your website, mainly because they complicate the workflow by needing the CSS to be compiled, and also increase the weight of the project. 

However, that doesn’t mean you cannot improve your styling workflow with pure CSS. CSS3 already provides a method to work with variables: custom properties and the var() function


Custom properties ( – – *)  and the var() function.

In CSS we can use custom properties in a way similar to how we use variables in other languages. 

For the purist, CSS variables are not really variables but more like “invalid properties” (since they don’t make any sense on their own), that get called and returned in other parts of the code with the var() CSS method. That is why they need to be written with a certain syntax.  

We’ll quickly check them out in action, but first… 

What are the main things you need to know?

  • Custom properties have to be written with two dashes before the name you want to give them. You then have to specify a value as you would with any other standard CSS property, including its units. For example:
--my-custom-color: #1b1b1b;
--my-custom-margin: 20px 10px;
--my-custom-font: 'Open Sans', sans-serif;
--my-custom-transition: all ease 0.3s;
  • They are case sensitive, so pay attention to the use of capital letters. Our advice? Forget about capitalization and just use kebab or snake case.
/* Pay attention when you write them, these are different properties */ 
--col-padding: 20px 15px;
--col-Padding: 40px 20px;
  • As they are properties, they can only be defined inside elements. We can make them global if we define them at the :root or body element −which means we can refer to them in any other element contained in the document−.
:root {
--color-text: #363636;
--color-accent: #64ccc9;
--font-base: Montserrat, sans-serif;
--font-display: Lobster, serif;
--col-padding: 20px 15px;
}
  • We can override them along the elements cascade, that is: they abide to the CSS priority rules
/* You can "update" the value of these custom properties in the nested elements */
body {
--standard-line-height: 1.5;
}
p.info {
--standard-line-height: 1.25;
}
  • They don’t do anything on their own. They need to be invoked using the var() function.
/* You need to call for these custom properties with the var() method */

body {
--standard-line-height: 1.5;
line-height: var(--standard-line-height); 
/* Now the body has a line height of the value of --standard-line-height, so: 1.5 */
}

p.info {
--standard-line-height: 1.25; 
/* The p.info inherits the body line height which is equal to
 --standard-line-height, but that variable has been updated 
at this element level, so it's 1.25 */
}
  • They don’t have any kind of validation or type. Only you are responsible for the correct use of the custom properties when invoking them as values.
/* If we have... */
:root {
--color-text: #363636;
--font-base: Montserrat, sans-serif;
--custom-padding: 20px;
}

/* ...this will result in an INVALID VALUES... */
h1 {
border: var(--color-text);
font: var(--font-base);
padding: var(--color-text);
}

/*...while this makes sense and will work. */
h1 {
border: 1px solid var(--color-text);
font-family: var(--color-text);
padding: var(--custom-padding);
}
  • You can write failproof code by using fallbacks, that is, giving a “backup” option in case the invoked variable value is not valid. You can add other option(s) as parameters in var(). Anything between the first comma and the end of the function will be the fallback, like:
    var(–my-custom-property, fallback);
/* In this case, if --color-text hasn't been defined, or if it is not a valid color, 
the final color for this h1 will be #333 */
h1 {
color: var(--color-text, #333);
}

/* Fallbacks can get as complicated as we want them to be, as we can also use other variables as fallbacks: */
/* Here, the priority of use will be as follows:
1) --my-custom-color
2) --backup-for-custom-color
3) ---backup-for-backup-color
4) black */
h1 {
color: var(--my-first-custom-color, var(--backup-custom-color, var(--backup-for-backup-color), black));
}

Advantages of using CSS variables.

Ok, we know how they work now, but… how can variables make your life styling better? Here are the main improvements you get.

Avoiding repetition.

If you are going to be referring to the same values in many elements of your code, the best practice would be to define a variable for them, so that you can call it as many times as you want.

Flexibility (easier changes) and control.

When your elements’ styles refer to a value specified in the start of the code, any modification has to be done just once. The alternative being having to look for all the repeated values and replacing them one by one, with the risk of overlooking or changing things you didn’t want to alter.

Semantic code.

While using #64ccc9 anywhere in your style doesn’t give much info for anyone reading it, writing –color-brand-accent is already giving a name to that value. It makes it easier to remember and easier to apply so, again, it can help you write code faster but, more importantly, it can make others understand and edit your code much more easily.

More room for creativity.

Concerning the rest of pros, building your CSS code around variables makes it easier, faster and painless to experiment with different styles (colours, fonts, spacing, layouts, etc.), so the workload you save by being more efficient can be invested in making things more appealing to the users (which, at the end of the day, is what CSS is for).


How to build an efficient stylesheet around CSS variables.

There is probably no magic recipe to the universal perfect stylesheet. Along the years, I’ve read and written CSS in very different ways and I’m still evolving with each new project. However, I believe to have found a structure that is optimal for me around the usage of CSS variables. I hope it can be helpful to you too! It goes like this:

0) Consider including a library, framework and/or reset file as base.

Including a framework, like Boostrap or Pure.css, is a choice that ideally should be done at the beginning of the project, but many times it comes later when certain new components arise the need for it. Think about how complex your layouts or components may be and decide if you want to build them from scratch or use an existing tool. In any case, to avoid style inconsistencies among browsers, it is also a good idea to use a CSS reset sheet.

Remember: if at any point you decide to get help from a framework or reset stylesheet, always include their CSS files before the ones you will be writing, so that your lines have priority over them and you don’t waste your hard work.

1) Start with the style/brand requirements palette at :root.

This is especially useful for me, as a person with a strong design background, but you don’t need to know much about design yourself to start here. The idea is that you are going to imagine the pieces you need to build up a branded front. Ask yourself things like:

  • Am I going to use more than one font for this website? Maybe one for titles, and one for content?
  • How many colours may this website have?
  • Do I want to cover both light and dark schemes? etc.

Then, little by little, create and name those custom properties in the :root element.

:root {
--color-accent: #64ccc9;
--color-text: #1b1b1b;
--font-size: 16px;
--font-basic: 'Open Sans', Helvetica, Arial, sans-serif;
--container-max-width: 800px;
...
}

You don’t need to fill these pieces with definite values yet! Just use placeholders. This may be important because sometimes you will be starting code before you have the design specs, but also because this will make it much easier to re-use your CSS in other projects. Be green! Write your code as something recyclable.

2) Go from general to specific: start with the basic HTML elements.

The next step is to start applying those custom values you’ve defined to the general structure of your website. Start from the parent and more generic elements, and go down to the details. For example, consider how you want things to look globally:

  • Generic text (body and p contents), colour, font type, starting size, line height, …
  • Titles size differences, paddings, colours, underlines or decorations.
  • Links behaviour with is pseudoclasses.
  • Transitions for changes in states, etc…

Don’t use custom classes yet, the idea is that you can style the starting structure just with the main HTML tags. Attend to the needs of your body and its elements ; ).

body {
font-family: var(--font-basic, sans-serif);
color: var(--color-text);
line-height: var(--line-height-basic);
}

h1, h2, h3, h4 {
font-family: var(--font-display, var(--font-basic, sans-serif));
font-weight: var(--font-bold, 700);
}

a, a:link, a:visited {
color: var(--color-accent, darkturquoise);
}
a:hover, a:active {
color: color: var(--color-accent-2, darkcyan);
}

...

While you style these tags and any others more specific, you are definitely going to need new custom properties you had not thought about before. That’s just normal. Go back to the top of your code and suit yourself adding new descriptive variables, although try not to overcomplicate things.

3) Define your layout structure.

In case you are not using any framework for this and want to go all out with flexbox or CSS grid properties, now would be the time to start creating your classes to define the layout elements in your design:

  • the basic containers,
  • headers, navigation menus, footer,
  • how rows and columns should behave, etc.
:root {
...
--container-max-width: 800px;
}

.container {
max-width: var(--container-max-width, 80%);
margin: 0 auto;
}

4) Media queries before it’s “too late”.

Before things get too intimate nested I strongly recommend that you consider how is your website going to behave in different devices. The layout and the basic HTML elements should be now re-styled or fixed to look good in other resolutions. Inside the media queries, you can update the variables that need to change with values more fitting to smaller/wider screen sizes. For example:

/* Small devices */
@media (min-width: 576px) { 
--container-max-width: 100%;
}

/* Large devices (desktops, 992px and up)*/
@media (min-width: 992px) { 
/* Our reference size, the global value fits so no update*/
}

/* Extra large devices (large desktops, 1200px and up) */
@media (min-width: 1200px) { 
--container-max-width: 1000px;
}

5) Custom classes for repetitive custom behaviours.

We’ve made it generic and responsive. Great start! But now you are going to be working with components that may need some particular changes. Now it is a good chance to create some additional class tags that allow us to customize some elements on the go in the HTML while keeping a simple CSS structure. This is a step that will help us avoid getting too specific when defining our components.

For example, you want a particular subtitle to be in a particular colour of the palette, but not the default one we chose while working at the general components level. What is the best way to solve it? Instead of creating another rule for that subtitle or a particular custom class for it, just atomize the needs inside separated tags and apply as needed. Create things like:

.text-bold {
font-weight: var(--font-bold, bold);
}

.text-accent {
color: var(--color-accent, bold);
}

.text-white {
color: var(--color-lightest, white);
}

/* Small devices */
@media (min-width: 576px) { 

.text-smaller-mobile {
font-size: calc( var(--font-size, 1rem) * 0.8 );
}

}

6) The most specific cases: custom component classes.

Finally, we get to style in details our custom components, using the existing global variables and also creating additional custom properties for these components as we need them.

Here you can choose between defining these new variables at a global level, so it can be easier to change everything in the same spot or define them at the component element level. The latter option makes much more sense when you are working with frameworks than doing your website in pure HTML, since that code will only be available if the component is included, therefore avoiding loading unnecessary CSS for other cases.

.magazine-grid {
display: flex;
--mag-grid-column: 33%;
}

.magazine-grid .mag-item {
max-width: var(--mag-grid-column);
}

.magazine-grid .mag-item .title {
...
}

.magazine-grid .mag-item .thumbnail {
...
}

.magazine-grid .mag-item .abstract {
...
}

Live example: CSS variables playground.

As they say, an image live code is worth a thousand words (at least, when you refactor it). Here is a small fiddle where you can play around with what we’ve just seen. I invite you to change the values in the definition of the variables at the beginning of the CSS and see the website change. Fast and neat, isn’t it?


Final tips for a better use of CSS variables

Here are some considerations to close this article that may come in handy. Maybe you’ve asked yourself…

– Would it be good to use variables for everything?

Definitely NO. Variables are handy tools to make your code more scalable and easy to change, but defining them and calling them takes longer than just writing styles on the fly. Also, abusing them complicates the code, backfiring the idea of using semantic values to make it more readable. The key for efficiency is to find a good balance between what is going to be needed frequently and the particular cases.

– Corollary: should all var() calls have a fallback?

Also no. It is important that you shield your code against potential invalid values, but not all cases are equally important, nor even needed, and fallbacks take time too.

If you work in an atomical way (like with most of the frontend frameworks) with different stylesheets, then in the process of merging them or the order between the elements, you may lose information. It is a good practice to always consider a fallback in those cases, especially for elements that can malfunction when inheriting the parent styles. For example, when you have a block with dark background in a light schemed website, setting a fallback to white will always make sure the text will be readable.

If, on the other hand, you are defining your global values in the same stylesheet you are going to access them, or you make sure that all the components are going to get their styles form a common palette, then why bother? It is more important that you check for errors on the live version, and pay special attention to the type of value you are calling, so that, i.g., you don’t end up having a font-size of ‘Time New Romans’. And to avoid the latter…

– How should I name my variables?

Taking the time to name code elements correctly is a very important part of coding, and it is a service you do to others and to your future self. Apart from being descriptive and clear, when I use CSS variables I find it helpful to start naming the global variables with what makes their values common. For example, it’s easier to remember if all your colour shades start by colour: –color-dark, –color-accent, –color-alert instead of dark-color, accent-color or alert-color. However, for the variables that are going to be specific for an element, then it is better to group them by the element name: like –h1-font, –h1-color, –h1-padding, etc. In the end, what is important is that the names allow you and others to easily remember what each variable stands for, in an orderly fashion.

– CSS variables, LET or CONST?

Since EC6, Javascript added two more specific definition options when declaring variables: let and const. While let is closer to the traditional definition of a variable that can be redefined and updated, const defines a variable that should remain constant. With this code allegory I want to open the debate: should CSS variables be updated along with the nesting of elements or defined as constant global values?

In my personal opinion, having custom properties as constants is more beneficial, for a matter of clarity and coherence. It also makes it faster to make changes as you don’t need to cover all code to see how you’ve rewritten the initial values. My favourite exception to this would be updating the values inside media queries, also at a global level.


To wrap it up, these choices and processes are the ones I’ve found to be most fitting for my workflow, but maybe you find other ways to apply CSS variables to yours. You have your own path to walk!

We would love to hear more about the way using variables has made it easier to style for you. Let us know in the comments below about your experience. We’ll also gladly answer your doubts or comments. Feel free to write and ask!

Thanks for your time, see you soon!

K Thx Bye!

For further reading, check out these links:

Leave a Reply

Your email address will not be published. Required fields are marked *