What no one told you about CSS Variables
Tags: css, webdev, beginners
Published: 2021-04-08 13:33:09

CSS Variables are great but do you know everything about them? 

In this post, I will highlight a few quirks around CSS variables that no one talks about. After that, you won't look at them the same way anymore.

----

## Table of content

1. [Be careful with `!important`](#1-be-careful-with-raw-important-endraw-)
2. [They cannot store urls](#2-they-cannot-store-urls)
3. [They can make an invalid value valid](#3-they-can-make-an-invalid-value-valid)
4. [They can be used unitless](#4-they-can-be-used-unitless)
5. [They can be animated](#5-they-can-be-animated)
6. [They cannot store the `inherit` value](#6-they-cannot-store-the-raw-inherit-endraw-value)
7. [They can be empty](#7-they-can-be-empty)
8. [CSS variables are not C++ variables](#8-css-variables-are-not-c-variables)
9. [They only work from parent to child](#9-they-only-work-from-parent-to-child)
10. [They can have strange syntaxes](#10-they-can-have-strange-syntaxes)

----

### 1) Be careful with `!important`

Using `!important` with CSS variables is a bit tricky so let's start with a basic example:

```css
p {
  --color:red!important;

  color:var(--color);
  color:blue;
}
```
What will be the color of `p`? you think it's `red` because we will have the following:

```css
p {
  color:red!important;
  color:blue;
}
```

But it's not! the color of `p` will be blue because we will have the following:

```css
p {
  color:red;
  color:blue;
}
```

`!important` in this case isn't part of the value of color but is used to increase the specificity of `--color`. From [the specification](https://www.w3.org/TR/css-variables-1/#syntax):

>Note: Custom properties can contain a trailing !important, but this is automatically removed from the property’s value by the CSS parser, **and makes the custom property "important" in the CSS cascade**. In other words, the prohibition on top-level "!" characters does not prevent !important from being used, as the !important is removed before syntax checking happens.

Here is another example to better understand:

```css
p{
  --color:red!important;
  --color:blue; 

  color:var(--color);
}
```
The above will give us a `red` color:

 1. We have two declarations of the same property called `--color` so we need to resolve the cascade. The first one is having `!important` so it wins
 2. We have our winner (`--color:red!important`) so `!important` is removed then the value is applied to `color`
 3. We have `color:red`.

Let's make our code:

```css
p{
  --color:red!important;
  --color:blue; 

  color:var(--color);
  color:blue;
}
```

Following the same logic, we resolve the cascade for `--color` and for `color`. `--color:red!important` is the winner and the same for `color:blue` so in the end we have `blue` because we no more care about `color:var(--color)`.

An *important* rule is to always consider CSS variables (custom properties) as ordinary properties and not only variables that store values.

>Custom properties are **ordinary properties**, so they can be declared on any element, are resolved with the normal inheritance and **cascade rules**, can be made conditional with @media and other conditional rules, can be used in HTML’s style attribute, can be read or set using the CSSOM, etc. <sup>[ref](https://www.w3.org/TR/css-variables-1/#syntax)</sup>

----

### 2) They cannot store URLs

This is a common limitation you will stumble upon one day.

#### What you cannot do ❌

```css
:root {
  --url:"https://picsum.photos/id/1/200/300";
}
.box {
  background:url(var(--url));
} 
```

#### What you should do ✔️

```css
:root {
  --url:url("https://picsum.photos/id/1/200/300");
}
.box {
  background:var(--url);
} 
```

This limitation is related to how `url()` is parsed. A bit tricky to explain but as we can see the fix is pretty easy. Always add the `url()` part within the CSS variable.

If you want more accurate detail, I advise reading this [Stack Overflow answer](https://stackoverflow.com/a/42331003/8620333)

----

### 3) They can make an invalid value valid!

This one is my favorite quirk and it's the one that will give you a lot of headaches.

Let's start with a basic example:

```css
.box {
  background: red;
  background: linaer-gradient(red, blue);
}
```

Our `.box` will have a gradient coloration ... wait, no it has a `red` background. Ah! I made a typo in `linear-*`. I can easily notice my mistake because the browser crossed the declaration and used the previous one.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eldcxl1boyhs2343058s.png)

Now, let's introduce a variable:

```css
.box {
  --color:red;
  background: var(--color);
  background: linaer-gradient(var(--color), blue);
}
```

Test the code and you will see that the background is now transparent and our second declaration is no more crossed because it's now a valid one. You will even notice that the first declaration is the one crossed because the second one overrides it.

#### What the hell is happening here ??!!

When using a variable within a property the browser will only evaluate the value of such property at "computed-value time" because we need to first know the content of the variable. In such cases, the browser will consider the value as *valid* when doing the cascade and only later it will become *invalid*.

In our case, the browser is considering the last declaration after resolving the cascade. Then when doing the evaluation, it seems to be invalid so it will be ignored. We won't get back to the previous declaration since we already resolved the cascade and we end with no background so a transparent one.

You may think such behavior is illogical but it's indeed logical because a value can be *valid* or *invalid* based on the CSS variable so the browser cannot know from the beginning.

```css
.box {
  --color:10px; /* a "valid" variable */
  background: red; /* a "valid" declaration */
  background:linear-gradient(var(--color),blue); /* a "valid" declaration that will override the first one  */
  /* The result is an "invalid" value ... */ 
}

```
>If a property contains one or more var() functions, and those functions are syntactically valid, **the entire property’s grammar must be assumed to be valid at parse time**. It is only syntax-checked at computed-value time, after var() functions have been substituted. <sup>[ref](https://www.w3.org/TR/css-variables-1/#using-variables)</sup>

and

>A declaration can be invalid at computed-value time if it contains a var() that references a custom property with its initial value, as explained above, or if it uses a valid custom property, but the property value, after substituting its var() functions, is invalid. **When this happens, the computed value of the property is either the property’s inherited value or its initial value** depending on whether the property is inherited or not, respectively, as if the property’s value had been specified as the unset keyword. <sup>[ref](https://www.w3.org/TR/css-variables-1/#invalid-variables)</sup>

To use easy words: a CSS variable will make the status of a property in a standby mode until we do the evaluation. Only after the evaluation, we can say if it's valid or invalid. If it's invalid then it's too late, we cannot get back to using another one.


A related [Stack Overflow question](https://stackoverflow.com/q/66127664/8620333)

----

### 4) They can be used unitless

Almost all the tutorials/courses will show you such example:

```css
:root {
 --p: 10px;
}
.box {
  padding: var(--p);
}
```

But you can also do the following:

```css
:root {
 --p: 10;
}
.box {
  padding: calc(var(--p)*1px);
}
```

Having the unit in the variable isn't mandatory and in some cases, it's even better to use a unitless value because adding a unit is fairly easy and we may need to use the same value with a different unit.

Here is one example among many (taken from [this answer](https://stackoverflow.com/a/60243510/8620333) )

{% codepen https://codepen.io/t_afif/pen/abpyrmq %}

Never forget this important feature. It will save you one day.


----

### 5) They can be animated

Initially, CSS variables are defined to be non-animatable properties as per [the specification](https://www.w3.org/TR/css-variables-1/#defining-variables):

>Animatable:	no

But things have changed and thanks to the new [`@property`](https://drafts.css-houdini.org/css-properties-values-api-1/#at-property-rule) we can do animation/transition with CSS variables.

The support is still low (especially on Firefox) but it's time to get to know this.

Find below some use cases where I am relying on such feature:

{% link  https://dev.to/afif/we-can-finally-animate-css-gradient-kdk %}

{% link  https://dev.to/afif/css-only-fragmentation-effect-using-one-element-18kg %}

I will be writing more articles to show the magic we can do with this. Stay tuned!


----


### 6) They cannot store the `inherit` value

Let's consider the following example:

```html
<div class="box">
  <div class="item"></div>
</div>
```
```css
.box {
  border:2px solid red;
}
.item {
  --b:inherit;
  border:var(--b);
}
```

Intuitively, we may think that `.item` will inherit the same border of its parent element because `--b` contains `inherit` but it won't (you can try and see).

As I explained in (1), the common mistake is to think that CSS variables will simply store a value that we can use later but not. CSS variables (custom properties) are **ordinary properties** so `inherit` *applies* to them and is not stored inside them.

Example:

```css
.box {
  --b:5px solid blue; /* we define the variable on the parent */
}
.item {
  --b:inherit; /* the child will inherit the same value so "5px solid blue"*/
  border:var(--b); /* we will have "5px solid blue" */
}
```

As you can see, the logic of inheritance applies to them the same way as with common properties.

Worth to note that doing the above is useless because CSS variables are by default inherited. It's like setting `inherit` to a property that is by default inherited (`color` for example).

This said I have elaborated a technique to be able to use CSS variables with `inherit`

{% stackoverflow 53239880 %}

The same logic applies to other keywords like `unset` and `revert`: [How to set CSS variable to the value unset, “--unset-it: unset”?](https://stackoverflow.com/q/61797468/8620333)

----

### 7) They can be empty

Yes you can do the following:

```css
.box {
  --color: ;
  background:var(--color); 
}
```
The above is valid as per [the specification](https://www.w3.org/TR/css-variables-1/#syntax):

>Note: While `<declaration-value>` must represent at least one token, that one token may be **whitespace**. This implies that `--foo: ;` is valid, and the corresponding `var(--foo)` call would have a single space as its substitution value, but `--foo:;` is invalid.

Pay attention to the last sentence because we need to have at least one space. The below is invalid:

```css
.box {
  --color:;
  background:var(--color); 
}
```
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/botya61f4c5j6dwrjxd8.png)

This quirk is mainly used with the fallback feature to do some magic.

A basic example to understand the trick:

```css
.box {
  background:
   linear-gradient(blue,transparent)
   var(--color,red); 
}
```
```html
<div class="box">
  I will have `background:linear-gradient(blue,transparent) red;`
</div>
<div class="box" style="--color:green">
  I will have `background:linear-gradient(blue,transparent) green;`
</div>
<div class="box" style="--color: ;">
  I will have `background:linear-gradient(blue,transparent)  ;`
</div>
```

1. The first box has no variable defined so the fallback will get used.
2. The second one has a variable defined so it will get used
3. The last one defined an empty variable so that *emptiness* will be used. It's like we no more have the `var(--color,red)`.

The empty value allows us to *remove* the `var()` declaration from a property! This can be useful when using `var()` within a complex value.

In case `var()` is used alone, the same logic applies but we will end uo having an empty value that is invalid for most of the properties.

If we took our first example we will have `background: ;` which will lead to an invalid value at "computed-value time" (remember the (3)) so a transparent background.

----

### 8) CSS variables are not C++ variables

Unfortunately, many developers tend to compare CSS variables to variables of other languages and end up having a lot of issues in their logic. For this specific reason, I don't want to call them variables but Custom properties because they are *properties*.

What everyone wants to do 

```css
:root {
  --p:5px;
  --p:calc(var(--p) + 1px); /* let's increment by 1px */
}
```
```css
:root {
  --x: 5px;
  --y: 10px;
  /* let's do a variable switch */
  --c: var(--y);
  --y: var(--x);
  --x: var(--c);
}
```

```css
.box {
  --s: 10px;
  margin:var(--s); /* I want 10px of margin */
  --s: 20px;
  padding:var(--s): /* then 20px of padding */
}
```

All the above will never work. The first two are simply invalid because we have a [*cyclic dependencies*](https://www.w3.org/TR/css-variables-1/#cycles) since a variable is referring to itself (first example) or a group of variables (the second example) and is creating a cycle . 

In The last example, both `padding` and `margin` will have `20px` because the cascade will give priority to the last declaration `--s: 20px` that will get applied to both `margin` and `padding`.

This said you should stop thinking C++, JavaScript, Java, etc when working with CSS variables because they are *custom properties* having their logic.


----

### 9) They only work from parent to child.

Remember this gold rule: CSS variables always *travel* from a parent element (or an ancestor) to child elements. They never *travel* from child to parent or between sibling elements.

This will lead us to the following mistake:

```css
:root {
  --c1: red;
  --c2: blue;
  --grad: linear-gradient(var(--c1),var(--c2);
}
.box {
  --c1: green;
  background:var(--grad);
}
```

Do you think the background of `.box` will be `linear-gradient(green, blue)`? No, it will be `linear-gradient(red, blue)`. 

The root element is the uppermost element in the DOM so its an ancestor of our `box` element and our gold rule says that we can only do *parent --> child* so `--c1` cannot go in the opposite direction to reach the root element, change `--grad` and then we get back in the other direction to re-send the changed value of `--grad`.

In such an example, the `.box` will inherit the value of `--grad`  defined with the values of `--c1` and `--c2` inside `:root`. Changing `--c1` will simply change the value of `--c1` inside `.box`, nothing more.

Find below a more detailed answer I wrote around this subject:

{% stackoverflow 52015737 %}

Even the [Stack Overflow team](https://stackoverflow.com/q/58206867/8620333) stumbled upon this quirk!

---

### 10) They can have strange syntaxes

A last and funny quirk. 

~~Did you know that you can do the following?~~ The following is no more valid. The Spec was updated to forbidden it.

```css
body {
  --:red;
  background:var(--);
}
```

~~Amazing, right? Yes, a CSS variable can be defined using only the two dashes. Related: [Is "--" a valid CSS3 identifier?](https://stackoverflow.com/q/67066696/8620333)~~

You think the above is crazy, take a look at the following:

```css
body {
 --📕:red;
 --📗:green; 
 --📘:blue;
 --📙:orange;
}
```

Yes, emojis! you can define your variables using emojis and it works.

The syntax of CSS variables allows almost everything the only requirement is to start with `--`. You can also start with a number (ex: `--1:`). Related: [Can a CSS variable name start with a number?](https://stackoverflow.com/q/66941906/8620333)

Why not only dashes:

```css
body {
  ---------:red;
  background:var(---------);
}
```

Or the same variable storing two different values

```css
body {
  --‎​:red;
  --‎:blue;
  background:linear-gradient(90deg, var(--‎​),var(--‎));
}
```

Try the above and you will get a gradient coloration!

To achieve such magic I am relying on a hidden character that makes both of the variables different but *visually* we see them the same. If you try the code on [jsfiddle.net](https://jsfiddle.net) You will see the following:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c3mxu4h4dnzqvn5t0wry.png)

Of course, you should never use such a thing in a real project unless you want to make your boss and coworkers crazy 😜


---

## That's it

I know it's a lot of information at once but you don't have to remember everything. I tried to group the most unknown and non-intuitive behaviors around CSS variables. If one day something is not working as expected, get back here. You will probably find your answer in the above.

I will end with some Stack Overflow questions I have answered that can be useful:

[How can I get a negative value of CSS variables in a calc() expression?](https://stackoverflow.com/q/48639882/8620333)

[How to create color shades using CSS variables similar to darken() of SASS?](https://stackoverflow.com/q/55329996/8620333)

[How to Use calc() to switch between color values?](https://stackoverflow.com/q/51063555/8620333)

[Can a recursive variable be expressed in css?](https://stackoverflow.com/q/49973885/8620333)

[Get computed value of CSS variable that uses an expression like calc](https://stackoverflow.com/q/56229772/8620333)

[Are CSS Variable changes possible upon a radio button's checked selector being triggered?](https://stackoverflow.com/q/55931363/8620333)

----

### <center>You want to support me?</center>

[![buy me a coffee](https://cdn.buymeacoffee.com/buttons/default-orange.png)](https://www.buymeacoffee.com/afif)

<center>**OR**</center>

[![Become a patron](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/db3jtgy2i5l4w8slkps5.png)](https://www.patreon.com/temani)