CSS variables: evolution or revolution?

18.02.2022 | Author: Przemysław Kosiński

What are variables?

Put briefly and simply, in programming a variable is a construct made up of two basic attributes: a name and a value.
Technically it is a bit more complicated, because a name and its value alone are not enough. Variables also have attributes such as a storage location and a type, which determines the kind of data assigned.
From a practical point of view we are usually interested only in the name and the value, although in advanced programming, where variables are modified, the type is also very important, because it determines how variables can be manipulated.
Creating variables gives us unlimited possibilities for using them later in the code in any way. We can modify them, assign them to other variables and display them. Variables can hold data sets such as arrays or objects, as well as fragments of code, functions and procedures.
The declaration of a variable may differ between programming languages, but the idea is the same everywhere.
You can read more about variables here: variables on Wikipedia

CSS

Cascading Style Sheets (CSS) are a simplified language, or rather a set of directives that define the presentation layer of an HTML document.
Over little more than twenty years of development, CSS, created by the W3C in 1996, has evolved from a simple list of directives defining the look of basic (X)HTML elements and tags into today’s more advanced version with considerable capabilities, approaching other advanced programming languages, drawing on and standardising some solutions known for years in other languages and in preprocessors (such as Less and Sass) that extend the capabilities of an ordinary style sheet.
The most popular version today is the third (CSS 3), which keeps some compatibility with previous versions, but unlike them is no longer one complete document. It has been divided into separate modules, which can be published as binding standards regardless of how far along or stable the others are.

CSS variables

One of these modules, described in this article, is variables, which quite recently moved out of the experimental phase into the first phase, with pretty good support in most modern desktop and mobile browsers. (css-vars)
At the moment, variables give us their basic functionality. With them we can store specific values we are interested in, for example any colour, which we can then use to set a font, a background or a border.
Declaring a variable means giving it a specific name, with the condition that it must start with two hyphens: --[name]: [value];

As in other languages, variables can be global, available to the whole style sheet, by declaring them in the :root directive :root { --color: red; }, or local to a specific declaration header { --color: red; }.
We refer to variables with the var(--variable-name) directive.
Variables are inherited through the cascade like other CSS directives.

Now the fun and the magic begin: a variable can hold any value or set of values, and we can modify them freely.
For example, we can store an image URL in a variable:

:root {
    --image-url: "https://picsum.photos/id/1/1200/640";
    --image-from-somewhere: url("https://picsum.photos/id/1/1200/640");
    --image-from-local: url("css2.jpg");
    --image-base: url("data:image/svg+xml;base64,PHN2ZyBjbGFzcz0id2hpdGUiIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDE0NiAzMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gPGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4gPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTEpIiBmaWxsPSIjZmZmIj4gPGcgY2xhc3M9ImFuaW1hdGlvbiBibGluayIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTI5IDI2KSIgZmlsbC1ydWxlPSJub256ZXJvIj4gPHJlY3QgeD0iLjU0MiIgeT0iLjUzMiIgd2lkdGg9IjE2LjgwMyIgaGVpZ2h0PSIzLjczNCI+PC9yZWN0PiA8L2c+IDxwYXRoIGQ9Im01NS4zMDcgMjEuODIycy0yLjAyMiA0LjIyMy0zLjkxMSA0LjIyM2MtMS4wNjg0LTAuMDQ1NzExLTEuOTk3My0wLjc0Ny0yLjMzNC0xLjc2MmwtMi4yMjMtNC4yMDljLTAuMzA0MTEtMC42OTYzOS0wLjgyNTg4LTEuMjc1NC0xLjQ4Ny0xLjY1di0wLjA4MWMwLjYzOTc1LTAuNDI2MjMgMS4xNzU5LTAuOTkwMzggMS41NjktMS42NTFsNS4zMzEtNy45MzRoLTUuODIybC00LjMgNi42NDdjLTAuNDk0ODQgMC41Njg4Ni0xLjIzNDcgMC44NjI1OC0xLjk4NSAwLjc4OGgtMS41Njl2LTE1Ljc0aC01LjI1djI5LjM2OWg1LjI1di05LjIxMmgxLjM0NWMwLjY2MiAwIDEuNTQ3IDAuMDM3IDEuOTQxIDAuNzg3bDIuODkyIDUuNTE4YzAuODg3MjcgMS44NzA4IDIuODE3NSAzLjAxOTUgNC44ODUgMi45MDcgMi44IDAgNC44MjYtMS45NDggNi4xNjQtMy45MTkgMC42MjEyNiAyLjM5MjggMi44MzAxIDQuMDI2MiA1LjMgMy45MTkgMi4zMTIxLTAuMTI4NjMgNC40MjY1LTEuMzQ1IDUuNy0zLjI3OXYzLjI3OWg1LjIwNXYtOS42NjZjLTAuMDEyOTQ4LTAuOTUyOTIgMC4xMjc1MS0xLjkwMTcgMC40MTYtMi44MSAwLjY4MDgzLTIuNTU3MiAzLjAxMTktNC4zMjUxIDUuNjU4LTQuMjkxIDIuNjQ3IDAgMy4zMDkgMS43MzIgMy4zMDkgNC4yOTF2NS4zNzVjMCAwLjkyMiAwLjEzNCA3LjEgNS44IDcuMSAyLjk1OSAwIDUuMDU2LTIuMTc5IDYuMzk1LTQuMjY4IDEuNzc2IDIuODYyIDUuMDg1IDQuNzY2IDEwLjIzIDQuNzY2IDUuMzkxIDAgOS41LTQuMjIzIDEwLjY2OS01LjUxIDAuNDI0IDIuMTE5IDEuNzEgNS4wMTIgNS41OTEgNS4wMTIgNS42NjYgMCA4LjE4Ni04IDguMTg2LThoLTIuNTIxcy0yLjAyMiA0LjIyMy0zLjkxMSA0LjIyMy0yLjUyMS0xLjg4OS0yLjUyMS0zLjE0NSAwLjEzNC01LjggMC4xMzQtNS44bDAuMDc0LTQuMTcyaDQuOHYtNC4xNzFoLTQuOHYtNS43NDdoLTUuMTE3djUuNzQ3aC0yLjg0OHY0LjE3MWgyLjcyOXY4LjQyNWMtMC4zNzIgMC40NTQtNC4zMzUgNS4xNzUtMTAuMDYxIDUuMTc1LTQuNDQ2IDAtNi44MzMtMi42MDktNy4yMi02LjMyN2gxMy42ODFzMC4xMTktMS4zMjMgMC4xMTktMS45NGMwLTUuNTc2LTMuMTA3LTEwLTguNzg4LTEwLTUuOTQxIDAtMTAuMDYgNC41NDMtMTAuMDYgMTEuMDM0LTAuMDA4NjA1IDEuMTU2MiAwLjEzMzU5IDIuMzA4NiAwLjQyMyAzLjQyOC0wLjcxMyAxLjI0Mi0yLjEgMy4zMjMtMy40MjcgMy4zMjMtNi43MjIgMCAzLjgxNC0xNy43ODUtOS42NjYtMTcuNzg1LTIuOTg5NS0wLjA4Njc1NC01Ljc4NDEgMS40NzkxLTcuMjcxIDQuMDc0di0zLjU3NmgtNS4yMDV2MTMuMDY0YzAgMC4xMTEtMi4wMjkgNC4yMjMtMy45NDggNC4yMjMtMS44ODggMC0yLjUxMy0xLjg4OS0yLjUxMy0zLjE0NSAwLTQuNyAwLjE0OS05LjQyOSAwLjE0OS0xNC4xNDNoLTUuMTlsM2UtMyAxMy4wNjV6bS01NC4zMDctNi44N2MtMC4wODg5MzQgNS40NzQ4IDIuNzgwOSAxMC41NzIgNy41MDggMTMuMzM2IDQuNzI3MSAyLjc2MzQgMTAuNTc3IDIuNzYzNCAxNS4zMDQgMHM3LjU5Ny03Ljg2MDkgNy41MDgtMTMuMzM2Yy0wLjEzNDQ1LTguMjc2Ni02Ljg4MjMtMTQuOTE2LTE1LjE2LTE0LjkxNi04LjI3NzcgMC0xNS4wMjYgNi42MzkxLTE1LjE2IDE0LjkxNnptNS42MTMgMGMtMC4xMzM5NS0zLjQ5NjYgMS42NTUxLTYuNzg2NCA0LjY2MjgtOC41NzQ1IDMuMDA3OC0xLjc4ODEgNi43NTI2LTEuNzg4MSA5Ljc2MDMgMCAzLjAwNzggMS43ODgxIDQuNzk2OCA1LjA3NzkgNC42NjI4IDguNTc0NSAwIDUuODY2LTQuMjUyIDEwLjMyNy05LjU0IDEwLjMyN3MtOS41NDYtNC40NjEtOS41NDYtMTAuMzI3em00OC45MzgtOS42MjFoNC41NTF2LTQuODMxaC00LjU1MXY0LjgzMXptNDEuNjA3IDExLjIzNGMwLjU0My0yLjY0NyAyLjMzNS00LjI1MyA0Ljg1NS00LjI1MyAyLjAyMiAwIDMuNjU4IDEuNzMyIDMuNzMyIDQuMjUzaC04LjU4N3oiPjwvcGF0aD4gPC9nPiA8L2c+IDwvc3ZnPgo=");
}

.lorem1 {
    background-image: url(var(--image-from-local));
}

.lorem2 {
    background-image: var(--image-from-somewhere);
}

.lorem3 {
    background-image: var(--image-from-local);
}

.lorem4:before {
    content: var(--image-base);
}

They can also be any other values, for example pixel values.

:root {
    // a set of values for each side of the element;
    --all-paddings: 10px 15px 20px 15px;

    // a value for one side of the element;
    --bottom-padding: 10px;
}

.lorem1 {
    padding: var(--all-paddings);
}
.lorem2 {
    padding: 0 0 var(--bottom-padding) 0;
}

Variables can hold a gradient, a shadow, a filter or a colour scheme adapted to users’ preferences.

:root {
    --grad: linear-gradient(90deg, rgba(131,58,180,1) 0%, rgba(253,29,29,1) 50%);
    --basic-shad: 0 0 5px 5px rgba(255,255,255,1);
    --multi-shad: 0 0 5px 10px rgba(255,0,0,1), 0 0 5px 15px rgba(0,255,0,1), 0 0 5px 20px rgba(0,0,255,1);

    --f-shad: drop-shadow(8px 8px 10px gray);
    --f-invert: invert(100%);
}

.lorem1 {
    background: var(--grad);
    box-shadow: var(--basic-shad);
}
.lorem2 {
    box-shadow: var(--multi-shad);
}
.lorem3 {
    box-shadow: var(--basic-shad), var(--multi-shad);
}
.lorem4 {
    filter: var(--f-shad) var(--f-invert);
}

@media (prefers-color-scheme: light) {
    :root {
        --global-color: #0d2f52;
        --global-background: #fff;
    }
    body {
        background: var(--global-background);
        color: var(--global-color);
    }
}
@media (prefers-color-scheme: dark) {
    :root {
        --global-color: #fff;
        --global-background: #0d2f52;
    }
    body {
        background: var(--global-background);
        color: var(--global-color);
    }
}

They can also be the result of a predefined function and, in more advanced cases, contain other variables or the results of other functions.

:root {
    --calc-result: calc((100vw - 1920px) / 2);
    --center: 0 auto;

    --min-font: 16;
    --max-font: 90;
    --min-res: 320;
    --max-res: 1920;

    --font-dif: calc(var(--max-font) - var(--min-font));
    --res-dif: calc(var(--max-res) - var(--min-res));
    --advanced-font-size: calc( (var(--min-font) * 1px) + var(--font-dif) * (100vw - (var(--min-res) * 1px)) / var(--res-dif) );
}

.lorem1 {
    display: block;
    width: 100%;
    max-width: var(--calc-result);
    margin: var(--center);
}

.lorem2 {
    font-size: var(--advanced-font-size);
}

We can even build a simplified counter/clock using a few tricks with animation and variables. Unfortunately it is not perfect, because CSS animations do not guarantee accurate timing, but more on that in the next article.

<!--
/*
    define a new property that is an integer
    with an initial value of 0
*/
@property --num {
    syntax: '<integer>';
    inherits: true;
    initial-value: 0;
}
/*
    define the animations
*/
@keyframes sec {
    0% { --num: 0; }
    100% { --num: 60; }
}
@keyframes min {
    0% { --num: 0; }
    100% { --num: 60; }
}
@keyframes ho {
    0% { --num: 0; }
    100% { --num: 24; }
}

/*
    and create the counter
*/
.counter *:before {
    counter-reset: ho var(--num) min var(--num) sec var(--num);

    animation-delay: 0s;
    animation-timing-function: linear;
    animation-iteration-count: infinite;

    content: '';
    display: inline-block;
}
.counter .h:before {
    content: counter(ho);

    animation-name: ho;
    animation-duration: 86400s;
}
.counter .m:before {
    content: counter(min);

    animation-name: min;
    animation-duration: 3600s;
}
.counter .s:before {
    content: counter(sec);

    animation-name: sec;
    animation-duration: 60s;
}
-->

Modifying variables and operations on them

There is no great philosophy to modifying them: if we need to, we can overwrite a previously declared value with another one anywhere, with the natural consequence that deeper in the declaration tree the new value will be inherited.
We can modify them from CSS as well as with JavaScript.

:root {
    --basic-var: 10px;
    --pos-x: 0;
    --pos-y: 0;
}

.dot {
    position: absolute;
    top: var(--pos-y);
    left: var(--pos-x);

    padding: var(--basic-ver);
}
.lorem2 {
    --basic-var: 15px;
    padding: var(--basic-ver);
}

We modify a variable with JavaScript as follows.

let doc = document.documentElement;
let elem = document.querySelector(".dot");

doc.addEventListener("mousemove", e => {
    elem.style.setProperty('--mouse-x', e.clientX + "px");
    elem.style.setProperty('--mouse-y', e.clientY + "px");
});

In addition, we can modify variables using predefined CSS functions, as in the earlier examples or as below:

:root {
    --variable1: 100vw;
    --variable2: 1024px;

    --container-width: min(var(--variable1), - var(--variable2));

    --font-size: 14px;
    --scale-factor: 1.2;

    --fz: calc(var(--font-size) * var(--scale-factor));
}

.lorem1 {
    max-width: var(--container-width);

    font-size: var(--fz);
    line-height: var(--scale-factor);
}

Summary

In my opinion, what is still missing at the moment is the ability to store whole blocks of styles with many declarations defining the look of complete elements, which could then be passed to other elements and modified as needed.
I also miss the ability to define your own functions that return specific values based on any amount of input data, but that will probably become part of the standard one day too.

Variables alone do not change much in small projects; they offer some possibilities and make it easier to organise code and make future changes.
In larger projects we usually use various frameworks, preprocessors and other tools that streamline the whole process of building a website or online store to organise and optimise code anyway,
while the continuous development of devices, browsers and technologies forces changes even in basic things like style sheets.
Will innovations like this completely revolutionise the approach to frontend and building layouts?
On their own, probably not, but combined with everything else they offer considerable possibilities.
Do not be afraid to experiment, and if something cannot be done, leave it to us ;).
See you next time.

Related technologies

Przemysław Kosiński Przemysław Kosiński

frontend developer

I am an experienced frontend developer. I remember the days of Internet Explorer 6, so nothing scares me ;). I am a fan of the new solutions in HTML5 and CSS3, which more and more natively make it possible to build modern, attractive features.

All articles by this author

Share

Rate this article

Let’s talk
about your project

+48 506 160 480
biuro@okinet.pl

or write to us