linkedin Skip to Main Content
Just announced: We now support interviewing in spreadsheets!
Back to blog

What Front-End Developers Need to Know about CSS Variables

Development

CSS variables (also known as custom properties) enable you to declare reusable and dynamic CSS values.

For a long time, a common challenge most web developers faced when working with CSS was the tendency to declare the same styles over and over again throughout the stylesheet. For example, on a website with a red color scheme, you might repeatedly declare the style color: red; throughout your stylesheets.

This can lead to issues in the maintainability of your CSS styles, such as having to edit all instances of the declaration color: red; in your stylesheets when the color scheme is to be changed to purple. Enter CSS variables.

CSS variables are author-defined property-value pairs, author-defined means they are not in the CSS specification but are defined by you, the web developer.

They provide a way to define a CSS declaration once and then reuse that declaration throughout the stylesheet. This enables you to write more dynamic CSS and provides a more efficient and clean way of writing CSS.

In this article, we’ll talk about CSS variables, what they are, how they work, and what you need to know to enable you to create more dynamic and reusable styles.

Let’s dive in!

What is a CSS variable?

In a programming language like JavaScript, a variable is defined as a way of storing information in the program—a storage unit. Each variable acts like a container, where you pass a name that acts as the label on the container and assigns that name to a value that represents the contents in the container. Once a variable is assigned a value, you can then use the name of the variable to access its value anywhere down in the program. CSS variables operate in such a manner.

To initialize a CSS variable, you define a property with a custom name of your choice and assign any valid CSS value to it.

Syntax:

element {
  --variable-name: css-value;	
}Code language: CSS (css)

CSS variables, like every CSS property, must be defined within the style block of a CSS selector.  

Here, element refers to any valid HTML element, the curly braces {} refers to the declaration block of the element, --variable-name and css-value refer to the name of the CSS variable and the value you assign to it.

Notice the double hyphens -- prefixed to the variable’s name. To define a CSS variable, you must prefix the double hyphens -- to the variable’s name (known as the custom property notation). This ensures you avoid accidental name overlapping of reserved CSS properties or other properties starting with a single hyphen – like -web-kit.

Now, you can reuse the values in your CSS variables as the value in other properties across your CSS styles by passing the variable name to the CSS var() function.

For example:

child-element {
  property: var(--variable-name);
}Code language: CSS (css)

The var() function parses the variable and returns its value.

Scope and the CSS Cascade

The CSS selector that defines a CSS variable determines how it cascades down through the stylesheet and the child elements of that HTML element.

In CSS, the cascade is a mechanism that determines how accessible a CSS style is to other parts of a stylesheet. It answers the questions: “from where can certain CSS styles be accessed?”, “from where can it not be accessed?”, “what can access it?” and “what can’t?”

Each HTML element you reference in CSS through CSS selectors creates its scope: the space/environment where the CSS properties and styles it defines can be accessed, which also determines how it accesses other CSS properties and styles via a process called cascading. 

Let’s look at an example to understand this better.

When you define an HTML element within another element, like so:

<outer-element> 
<!-- also known as the “parent element” -->    
  <inner-element>
      <!-- also known as the “child element” -->
  </innner-element>    
</outer-element>Code language: HTML, XML (xml)

The inner element will be able to inherit and access some of the CSS styles defined in that of the outer element CSS ruleset and that of its parent elements.

However, this doesn’t work in a backward-compatible manner. That is, the outer element will never have access to the CSS styles defined in the CSS ruleset of the inner element because CSS styles only cascade downwards in the stylesheet in a parent-to-child direction and never in the opposite direction. 

ℹ️ This behavior also applies to variables in other programming languages and is called scoping.

As a result, it is a common practice to define CSS variables in the :root pseudo-class.

ℹ️ MDN defines CSS pseudo-class as a keyword added to a selector that specifies the selected element(s) state. For example, the pseudo-class :hover, signifies that a user’s pointer is hovering over an element.

The :root pseudo-class represents the element at the document’s root in the DOM. In HTML, this is usually the <html> element.

This means the syntax:

:root {
  --font-size: 16px;
}
Code language: CSS (css)

Is the same as writing: 

html {
  --font-size: 16px;
}Code language: CSS (css)

The difference is that :root has a much higher specificity. It’s simply a way of setting CSS variables on a selector that’s as high up in the document tree as possible. This way, they are made globally accessible across your CSS file and cascade to every element on the page.

However, this doesn’t always have to be the case, as you might want to limit the scope of your CSS variables.

In such cases, you can specify CSS variables on the style block of specific HTML elements to which you wish them to be accessible. 

For example:

nav {
  --font-size: 10px; 
/* equals to two times the value of the --font-size variable - 20px */
  font-size: calc(var(--font-size * 2)) 
}

nav a {
/* equals to the value of the --font-size variable - 10px */
  font-size: var(--font-size) 
}
Code language: CSS (css)

In the code block above, the CSS variable --font-size is defined on the navigation element — nav and will only be accessible within it and its child elements.

Assigning CSS variables to other CSS variables

You can pass a CSS variable as the value for another CSS variable using the var() function. The CSS variable will then be parsed by the var() function to represent its value.

For example:

:root {
  --red: #a24e34;
  --green: #01f3e6;
  --yellow: #f0e765;

  --danger: var(--red); // will transpile back to the value of the --red variable.
  --success: var(--green);
  --warning: var(--yellow);
}Code language: PHP (php)

The added benefit here is readability since you now communicate the intent or role of the variable. This can help you write variable names that are more readable, descriptive, and understandable.

Fallback values

When using CSS variables, you might reference a variable that can not be accessed in that part of the stylesheet due to the CSS cascade or was just not defined. Using the var function, you can specify fallback values that will be used in place of a CSS variable if it is inaccessible, contains invalid values, or is undefined.

The var() function takes two arguments:

var( <css-variable-name>, <declaration-value>)Code language: HTML, XML (xml)

Where:

  • <css-variable-name> is the name of a CSS variable to parse.
  • <declaration-value> is the fallback values used when the referenced CSS variable is inaccessible. It is an optional argument.

Let’s look at an example of specifying fallback values in the var function.

:root {
  background-color: rgb(111, 6, 146);
}

body {
  background: var(--background-colour, royalpurple, rebeccapurple, purple); 
 /* There is no --background-colour, hence the fallback values will be used */
}Code language: CSS (css)

In the code block above, we define the CSS variable background-color in the :root pseudo-class. But, when using it in the body tag style block, it is written as background-colour in British English and not background-color as defined in the :root pseudo-class. This will cause the variable to be undefined, and the browser to use the first fallback values.

While the var() function only accepts two arguments, in the code block above, it receives four comma-separated values. This is because everything after the first comma is regarded as the second parameter—fallback values. This means you can specify multiple fallback values, and if, for some reason, the first fallback value can’t be used, the next value would kick in, and so on.

However, this will not work when specifying other CSS variables as fallback values.

.element {
  background-color: var(--default-color, --backup-color, purple);
  /* will be Invalid: "--backup-color, purple" */
}Code language: CSS (css)

The CSS style above will be invalid because though you can specify CSS variables as fallback values, they still need to be nested in a var() function for their values to be parsed and returned.

.element {
  background-color: var(--default-color, var(--backup-color, purple));
}Code language: CSS (css)

The code above is the right syntax to provide a CSS variable as a fallback. Here, the color purple will only be used if --default-color and --backup-color are invalid or undefined.

Another use case where fallback values are useful is when the value of a CSS variable is not a valid value for the property to which it is supplied.

For example:

:root { 
  --text-red: 14px; 
} 

body { 
  color: var(--text-red, red); 
}Code language: CSS (css)

In this snippet, the CSS variable --text-red is defined with a value of 14px. This is an invalid value when passed to the color property. Hence. The fallback value of red will be used.

💡 Note that if the values passed to the second parameter are not supported by the browser or are invalid, the fallback will not be applied

What you can do with CSS variables

CSS variables are an amazing CSS feature, and there are no boundaries to what can be achieved using this tool. Let’s go over some of the things you can accomplish with CSS variables.

Assigning valid data types to CSS variables

Every CSS property has a set of values it can accept. For example, the CSS property color accepts only keyword values that describe a color like green or a numerical color value. These values are categorized into what are called data types.

CSS data types are a set of acceptable values and units you can assign to CSS properties—from keyword values to length units to CSS functions. Most CSS properties accept only a few data types as their value. However, there are no limits to what you can assign as the value of a CSS variable. They accept all valid data types.

In this section, you’ll look at most of these data types you can assign to CSS variables. You can get a more comprehensive reference for all CSS data types in the CSS specification. Let’s dive in.

Colors

CSS variables can be assigned CSS color keyword values and numerical color values such as hexadecimal color codes. This opens up a ton of flexibility in creating color themes in your stylesheets.

Say you’re using a CSS color function, like the rgba() or hsl() function notations. You can split the component values of these functions into independent CSS variables, and easily adjust each component’s value where you want.

That opens up many possibilities, like changing the alpha value for a specific use case or perhaps creating color themes.

For example, when working on the background color of a button with the hsl color function. By storing each component value of the function into CSS variables, you can update specific parts of the hsl() function when the button is in focus, hovered or disabled, without explicitly updating the background property of the button repeatedly in our stylesheet.

Take a look at the snippet below:

.btn {
  --hue: 216;
  --saturation: 85%;
  --lightness: 28%;
  --a: 1;

  background: hsl(var(--hue), var(--saturation), var(--lightness), var(--a));
}

.btn:hover {
  /* Change the lightness on hover */
  --l: 45%;
}

.btn:focus {
  /* Change the saturation on focus */
  --s: 50%;
}

.btn[disabled] {
  /* Make the button look disabled */
  --s: 0%;
  --a: 0.8;
}Code language: CSS (css)

Here, by storing each value of the hsl() function into CSS variables, you can create a theme for the hover, focus, and disabled state of a button by simply tweaking the values of each variable down in the stylesheet. Rather than re-updating the background property of the button, we overrode the specific hsl values in the correct context they are needed. That’s some cool stuff there.

Check out the demo here:

Numeric units

There are various numeric data types in CSS, such as:

  • Integers— whole numbers such as 100 or -45.
  • Numbers— decimal digits that may or may not have a decimal point with a fractional component such as 0.255, 128, or -1.2.
  • Dimensions— decimal digits suffixed with a CSS length unit such as 10px, 45deg, 0.5s.
  • And percentages— numbers suffixed with the percentage sign (%) representing a fraction of some other value. For example, 50%.

They are all assignable as values to CSS variables.

For example:

:root{
--border-size: 2.5px;
--scale-value: 1.5;
}

h1 {
  border: var(--border-size) solid black;;
}

h1:hover {
  transform: scale(var(--scale-value));
}Code language: JavaScript (javascript)

Strings

You can assign strings—text characters enclosed in single or double quotes—as values to certain CSS properties. For example, the content property, when used on the ::before and ::after pseudo-elements, can accept a string value that’ll be inserted into the DOM. You can assign string values to CSS variables as well. 

In the CoderPad sandbox above, the CSS variable --status is defined on a ul element with a string value of "New!". The --status variable is then used with the CSS content property on the ::after pseudo-element of each li element in the list to insert additional decorative text. Due to the CSS cascade, we can alter the --status variable in the style block of each li element to insert a different text at its end. 

Multiple data types

Typically, you can only assign a single CSS value to a property. However, some CSS properties can accept multiple CSS values of different types. They are called shorthand properties.

CSS shorthand properties let you set the values of multiple related CSS properties simultaneously in a single declaration. For example, the CSS property font is a shorthand property that accepts multiple CSS values that lets you specify the most common font-related properties like font-style, font-weight, etc. Similarly, you can assign multiple CSS values of different data types to a CSS variable. 

A good example would be the CSS border property. It accepts three values that specify the border’s width, style, and color.

Now, instead of storing these values in different CSS variables:

:root{
  --border-width: 1px;
  --border-style: solid;
  --border-color: #000;
}

.box{
  border: var() var() var();
}Code language: CSS (css)

You can store them all in a single CSS variable and pass it to the property.

:root{
  --border-box: 1px solid #000;
}

.box{
  border: var(--border-box);
}Code language: CSS (css)

ℹ️ Note the values of a CSS variable passed to a shorthand property must match the syntax of the values the shorthand property expects. Else, such values will be considered invalid for the property and will not be applied.

Use CSS variables with calc()

You can perform arithmetic operations on CSS variables using the calc() function. A good use case would be when you need certain values to be in a specific ratio.

For example:

:root {
  --base-text: 16px;

  /*wil be 2 times the value of --base-text */
  --big-text: calc(var(var(--base-text) * 2));

/* will be half the value of --base-text */
  --mini-text: calc(var(var(--base-text) / 2));
}

.h1 {
  font-size: var(--big-text);
}

.sub-text {
  font-size: var(--mini-text);
}Code language: CSS (css)

In the code block above, we use the calc() function to ensure that the --big-text variable will always be twice the value of --base-text and --mini-text half it’s value, regardless of what --base-text is. This helps us avoid hardcoding such values from scratch.

Another use case for using the calc() function with CSS variables would be to combine different CSS units to create values that are impossible to represent using traditional CSS units.

For example:

--nav-height: 8rem;
--main-height: calc(100vh - var(--nav-height));;
}

main {
  height: var(--main-height);
}

nav {
  height: var(--nav-height);
}Code language: CSS (css)

Here, we ensure the main section will take up most of the viewport’s height except for the height of the navigation bar by using the calc() function to subtract the navigation bar height, 8rem, from the viewport height 100vh in the --main-height variable.

Now, no matter what the viewport height is, the main tag will always be 8rem shorter than the full height of the screen.

Change CSS variables lower in the cascade

You can change the values of CSS variables lower in the cascade.

For example, you can overwrite the value of a global variable defined in the :root pseudo-class in the stylebook of another element.

:root{
  --base-font: 16px;
}

.hero-container {
  --base-font: 20px;
  font-size: var(--base-font)
}Code language: CSS (css)

This way, the --base-font variable will be set to 16px for all other elements in the DOM except for the element with the hero-container class and all its child elements, where it will be set to 20px.

Use cases

There are numerous applications for using CSS variables in your stylesheet. Let’s go over some of these use cases.

Implement styles that are conditionally rendered

Whenever there’s a need to implement dynamic styles, i.e, styles that change based on the application’s state, you’d usually opt for a solution using JavaScript or JSX. With CSS variables, you can shift such tasks away from JavaScript and more towards CSS and declare conditionally rendered styles using CSS only. 

Let’s look at an example of implementing a dark-mode and light-mode color theme for a website based on the user’s user agent (operating system) theme preference using CSS only.

If you weren’t using CSS variables, you’ll probably have a .dark-theme and .light-theme class that will hold the styles for each theme, then use JavaScript to conditionally apply the .dark-theme or .light-theme class to the html element based on the user’s color scheme.

With CSS variables, we won’t need classes to hold the styles for each theme. We can merely use the prefers-color-scheme media query to detect the user’s theme preference and store the colors for each theme in CSS variables defined in the :root pseudo-element.

body {
  background-color: var(--background-color);
  color: var(--text-color);
}

@media (prefers-color-scheme: light) {
  :root {
    --background-color: #fff;
    --text-color: #000;
  }
}

@media (prefers-color-scheme: dark) {
  :root {
    --background-color: #000;
    --text-color: #fff;
  }
}Code language: CSS (css)

This is possible because CSS variables cascade; hence variables declared in the :root which is the highest element in the DOM, can be accessed by all child elements. 

Here’s a CoderPad sandbox demo:

Access CSS variables in JavaScript

A selling point for using native CSS variables over variables in preprocessor languages like Sass, Less, or Stylus is that you can easily access, create and update CSS variables with JavaScript.

CSS variables are scoped to the DOM and its elements; hence they can be accessed in JavaScript using the getPropertyValue and setPropertyValue browser APIs.

The getPropertyValue method returns the value of a CSS property specified on an element.

For example, the snippet below will return the value of the CSS property --theme, which is a CSS variable defined on the :root element.

const root = document.documentElement;

const theme = root.style.getPropertyValue(“--theme”);Code language: JavaScript (javascript)

The setPropertyValue method sets the value for a CSS property specified on an element or declares it if that property isn’t defined.

For example, the snippet below sets the value of the :root element CSS variable --base-color to #000 when the user clicks dark-theme-btn element. If the variable doesn’t exist, it will be declared with that value.

const dark-theme-btn = document.getElementByid(“dark-theme-btn”)

dark-theme-btn.onclick = (e) => {
  e.preventDefault()
  document.documentElement.setPropertyValue(“--base-color”, “#000”)
}Code language: JavaScript (javascript)

CSS variables are dynamic. The browser will repaint the DOM with their updated values when they change (via JavaScript, or with a media query).

In the code block above, the horizontal and vertical positions of the CoderPad logo are stored with CSS variables. We use JavaScript to set those variables’ values to the mouse’s position as it moves along the screen.

This doesn’t apply to variables in preprocessor languages like SCSS because they resolve to a value when compiled and stay at that value.

Try out CSS variables too!

To recap, CSS variables enable you to create dynamic and reusable styles. They are an alternative to using preprocessor variables but offer much more benefits, such as their dynamic nature to repaint the browser whenever they change, their integration with the cascade and the DOM—something preprocessor variables will never be able to do, and the fact you can use them without a need to preprocess your CSS.

CSS variables allow you to store values and reuse them throughout your stylesheet, providing an easy way to make changes in one place and see the result across the whole stylesheet. This makes editing and revising the sheet easier for you and other developers when they try their hand at your CSS styles. Trust me – it’s worth knowing!

What are you waiting for? Try out CSS variables today!

This article was written by Victor Ikechukwu. Victor is a front-end developer and technical writer interested in the JAMstack. He enjoys breaking down developer concepts into clear, understandable bits for others.