How to use variables in CSS

CSS Variables (officially known as custom properties) are user defined values that can be set once and used many times throughout your codebase. They make it easier to manage colors, fonts, size, and animation values, and ensure consistency across web applications.

For example, you can set a brand color as a CSS property ( --primarycolor: #7232FA) and use this value in any components or style that uses your brand color (background: var(--primarycolor);).

Besides offering cleaner and non-repetitive code, CSS variables can be used to build color palettes, improve responsiveness, and create dynamic type systems.

This post is extracted from my guide, CSS Master, which teach you to write better, more efficient CSS. You’ll also learn to master tools that will improve your workflow and build better applications.

Defining a CSS Variable

To define a custom property, select a name and prefix it with two hyphens. Any alphanumeric character can be part of the name. Hyphen (-) and underscore (_) characters are also allowed. A broad range of Unicode characters can be part of a custom property name. This includes emoji, but for the sake of clarity and readability, stick to alphanumeric names.

Here’s an example:

--primarycolor: #0ad0f9ff; 

The -- indicates to the CSS parser that this is a custom property. When used as a variable, the parsing engine replaces the property with its value.

Custom property names are case-sensitive. That means --primaryColor and --primarycolor are considered two distinct property names. That’s a departure from traditional CSS, in which property and value case don’t matter. It is, however, consistent with the rules for variable names in ECMAScript.

As with other properties, such as display or font, CSS custom properties must be defined within a declaration block. One common pattern is to define custom properties using the :root pseudo-element as a selector:

:root 
  --primarycolor: #0ad0f9ff;

:root is a pseudo-element that refers to the root element of the document. For HTML documents, that’s the  element. For SVG documents, it’s the  element. Using :root makes properties immediately available throughout the document.

Using a CSS Variable

To use a custom property as a variable, we need to use the var() function. For instance, if we wanted to use our --primarycolor custom property as a background color, we’d do the following:

body 
    background-color: var(--primarycolor);

Our custom property’s value will become the computed value of the background-color property.

To date, custom properties can only be used as variables to set values for standard CSS properties. You can’t, for example, store a property name as a variable and then reuse it. The following CSS won’t work:

:root 
    --top-border: border-top; 
    var(--top-border): 10px solid #bc84d8; 

You also can’t store a property–value pair as a variable and reuse it. The following example is also invalid:

:root 
    --text-color: 'color: orange'; 

body 
    var(--text-color); 

Lastly, you can’t concatenate a variable as part of a value string:

:root 
    --base-font-size: 10;

body 
    font: var(--base-font-size)px / 1.25 sans-serif; 

CSS Custom Property vs. CSS Variable

“Custom property” is a future-proof name that accounts for how this feature might be used someday. This could change, however, should the CSS Extensions specification be implemented by browser vendors. That specification defines ways to extend CSS with custom selector combinations, functions, and at-rules.

We commonly call custom properties “variables”, and to date, that’s the only way we can use them. In theory, they’re not entirely interchangeable terms. In practice and for now, they are. I’ll mostly use custom properties in this post, since that’s their proper name. I’ll use variables when it makes the sentence clearer.

Setting a Fallback Value

The var() function accepts up to two arguments. The first argument should be a custom property name. The second argument is optional, but must be a declaration value. This declaration value functions as a fallback or default value that’s applied when the custom property value isn’t defined.

Let’s take the following CSS:

.btn__call-to-action 
    background: var(--accent-color, deepskyblue);

If --accent-color is defined—let’s say its value is #f30 —then the fill color for any path with a .btn__call-to-action class attribute will have a red-orange fill. If it’s not defined, the fill will be a deep sky blue.

Declaration values can also be nested. In other words, you can use a variable as the fallback value for the var function:

body 
    background-color: var(--books-bg, var(--arts-bg));

In the CSS above, if --books-bg is defined, the background color will be set to the value of the --books-bg property. If not, the background color will instead be whatever value was assigned to --arts-bg. If neither of those are defined, the background color will be the initial value for the property—in this case, transparent.

Something similar happens when a custom property has a value that’s invalid for the property it’s used with. Consider the following CSS:

:root 
    --text-primary: #600;
    --footer-link-hover: #0cg; 

body 
    color: var(--text-primary);

a:link 
    color: blue;

a:hover 
    color: red;

footer a:hover 
    color: var(--footer-link-hover);

In this case, the value of the --footer-link-hover property is not a valid color. Instead, footer a:hover inherits its color from that of the  element.

Custom properties are resolved in the same way other CSS values are resolved. If the value is invalid or undefined, the CSS parser will use the inherited value if the property is inheritable (such as color or font), and the initial value if it’s not (as with background-color).

Cascading Values

Custom properties also adhere to the rules of the cascade. Their values can be overridden by subsequent rules:

:root 
    --text-color: #190736; 

body 
    --text-color: #333;  

body 
    color: var(--text-color);

In the example above, our body text would be dark gray. We can also reset values on a per-selector basis. Let’s add a couple more rules to this CSS:

:root 
    --text-color: #190736; 

body 
    --text-color: #333;   

p 
    --text-color: #f60;  

body 
    color: var(--text-color);

p 
    color: var(--text-color)

In this case, any text that’s wrapped in 

 element tags would be orange. But text within 

 or other elements would still be dark gray.

You can also set the value of a custom property using the style attribute—for example, style="--brand-color: #9a09af".

Custom Properties and Color Palettes

Custom properties work especially well for managing HSL color palettes. HSL stands for hue, saturation, lightness. It’s a light-based color model that’s similar to RGB. We can use HSL values in CSS thanks to the hsl() and hsla() color functions. The hsl() function accepts three arguments: hue, saturation, and lightness. The hlsa() function also accepts a fourth argument, indicating the color’s alpha transparency (a value between 0 and 1).

While an RGB system expresses color as proportions of red, green, and blue, HSL uses a color circle where hue is a degree position on that circle, and the tone or shade are defined using saturation and lightness values. Saturation can range from 0% to 100%, where 0% is gray and 100% is the full color. Lightness can also range from 0% to 100%, where 0% is black, 100% is white, and 50% is the normal color.

Chromatic Wheel by CrazyTerabyte from Openclipart.

In the HSL color system, the primary colors red, green, and blue are situated 120 degrees apart at 0 degrees/360 degrees, 120 degrees, and 240 degrees. Secondary colors—cyan, magenta, and yellow—are also 120 degrees apart, but sit opposite the primary colors, at 180 degrees, 300 degrees, and 60 degrees/420 degrees respectively. Tertiary, quaternary, and other colors fall in between at roughly ten-degree increments. Blue, written using HSL notation, would be hsl(240, 100%, 50%).

HSL Argument Units

When you use a unitless value for the first argument of the hsl() and hsla() functions, browsers assume that it’s an angle in degree units. You can, however, use any supported CSS angle unit. Blue can also be expressed as hsl(240deg, 100%, 50%)hsl(4.188rad, 100%, 50%) or hsla(0.66turn, 100% 50%).

Here’s where it gets fun. We can set our hue values using a custom property, and set lighter and darker shades by adjusting the saturation and lightness value:

:root 
    --brand-hue:      270deg;  
    --brand-hue-alt:  .25turn; 

  

    --brand-primary:   hsl( var( --brand-hue ) 100% 50% );
    --brand-highlight: hsl( var( --brand-hue ) 100% 75% );
    --brand-lowlight:  hsl( var( --brand-hue ) 100% 25% );
    --brand-inactive:  hsl( var( --brand-hue )  50% 50% );

    --brand-secondary:     hsl( var( --brand-hue-alt ) 100% 50% );
    --brand-2nd-highlight: hsl( var( --brand-hue-alt ) 100% 75% );
    --brand-2nd-lowlight:  hsl( var( --brand-hue-alt ) 100% 25% );
    --brand-2nd-inactive:  hsl( var( --brand-hue-alt )  50% 50% );

The CSS above gives us the palette shown below.

Using custom properties with the HSL function to generate a color palette

This is a simple version, but you can also use custom properties to adjust saturation and lightness values.

Robust Palette Generation

Dieter Raber discusses a technique for robust palette generation in “ Creating Color Themes With Custom Properties, HSL, and a Little calc() ”.

Another idea is to combine custom properties and the calc() function to generate a square color scheme from a base hue. Let’s create a square color scheme in our next example. A square color scheme consists of four colors that are equidistant from each other on the color wheel—that is, 90 degrees apart:

:root 
    --base-hue: 310deg; 
    --distance: 90deg;

    --color-a: hsl( var(--base-hue), 100%, 50% );
    --color-b: hsl( calc( var(--base-hue) + var( --distance ) ), 100%, 50% );
    --color-c: hsl( calc( var(--base-hue) + ( var( --distance ) * 2 ) ), 100%, 50% );
    --color-d: hsl( calc( var(--base-hue) + ( var( --distance ) * 3 ) ), 100%, 50% );

This bit of CSS gives us the rather tropical-inspired color scheme shown below.

Generating a square color scheme from a base hue using an HSL function to generate a color palette

Custom properties also work well with media queries, as we’ll see in a later section.

Using CSS variables to make a Dark Theme palette

You can use CSS Custom Properties to define sets of variables for both dark and light themes on your site.

Take the below example of a page’s styles, we can replace all HSL colors in different selectors with variables after defining custom properties for the corresponding colors in :root:

:root
  
  --nav-bg-color: hsl(var(--primarycolor) , 50%, 50%);
  --nav-text-color: hsl(var(--primarycolor), 50%, 10%);
  --container-bg-color: hsl(var(--primarycolor) , 50%, 95%);
  --content-text-color: hsl(var(--primarycolor) , 50%, 50%);
  --title-color: hsl(var(--primarycolor) , 50%, 20%);
  --footer-bg-color: hsl(var(--primarycolor) , 93%, 88%);
  --button-text-color: hsl(var(--primarycolor), 50%, 20%);

Appropriate names for the custom properties have been used. For example, --nav-bg-color refers to the color of the nav background, while --nav-text-color refers to the color of nav foreground/text.

Now duplicate the :root selector with its content, but add a theme attribute with a dark value:

:root[theme='dark']
  

This theme will be activated if a theme attribute with a dark value is added to the  element.

We can now play with the values of these variables manually, by reducing the lightness value of the HSL colors to provide a dark theme, or we can use other techniques such as CSS filters like invert() and brightness(), which are commonly used to adjust the rendering of images but can also be used with any other element.

Add the following code to :root[theme="dark"]:

:root[theme='dark'] 
  --dark-hue: 240;
  --light-hue: 250;
  --primarycolor: var(--dark-hue);
  --nav-bg-color: hsl(var(--primarycolor), 50%, 90%);
  --nav-text-color: hsl(var(--primarycolor), 50%, 10%);
  --container-bg-color: hsl(var(--primarycolor), 50%, 95%);
  --content-text-color: hsl(var(--primarycolor), 50%, 50%);
  --title-color: hsl(--primarycolor, 50%, 20%);
  --footer-bg-color: hsl(var(--primarycolor), 93%, 88%);
  --button-text-color: hsl(var(--primarycolor), 50%, 20%);
  filter: invert(1) brightness(0.6);

The invert() filter inverts all the colors in the selected elements (every element in this case). The value of inversion can be specified in percentage or number. A value of 100% or 1 will completely invert the hue, saturation, and lightness values of the element.

The brightness() filter makes an element brighter or darker. A value of 0 results in a completely dark element.

The invert() filter makes some elements very bright. These are toned down by setting brightness(0.6).

A dark theme with different degrees of darkness:

A dark theme

Switching Themes with JavaScript

Let’s now use JavaScript to switch between the dark and light themes when a user clicks the Dark/Light button. In your HTML add an inline