Skip to main content

CSS Variables (Custom Properties)

CSS Variables, officially called Custom Properties, allow you to define reusable values (like colors, font sizes, or spacing amounts) in one place and reuse them throughout your stylesheets.

They bring the efficiency of variables from programming languages directly into CSS, dramatically improving maintainability, consistency, and the ability to implement dynamic theming.


1. Defining and Using Variables

1.1. Declaration

Custom properties are declared with a name starting with two hyphens (--).

1.2. Scope

The scope defines where the variable can be accessed.

  • Global Scope: Declared on the :root pseudo-class (which is equivalent to the <html> element). These variables are available everywhere in the document.
  • Local Scope: Declared on a specific selector (e.g., .card). These variables are only available to that element and its descendants.

Example: Global and Local Variables

styles.css
/* Global Scope: Accessible everywhere */
:root {
--primary-color: #3B82F6; /* Blue */
--spacing-base: 1rem;
}

/* Local Scope: Only accessible within .card and its children */
.card {
--card-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
background-color: white;
border-radius: 8px;
padding: var(--spacing-base);
box-shadow: var(--card-shadow);
}

1.3. Usage (var() function)

To retrieve the value of a Custom Property, you use the var() function.

styles.css
.button {
/* Use the global variable defined on :root */
background-color: var(--primary-color);

/* Use the global spacing variable */
margin-top: var(--spacing-base);
}

2. Fallbacks (Default Values)

The var() function accepts an optional second argument, which acts as a fallback value. If the specified variable is not defined or is invalid, the browser will use the fallback instead.

This is useful for local components where a variable might be optional.

styles.css
.title {
/* If --title-color is defined, use it. Otherwise, use black. */
color: var(--title-color, black);
}

3. Benefits of Using Variables

A. Consistency and Maintenance (DRY Principle)

Changing a single variable definition updates every instance of that value across your entire application. This is essential for maintaining a consistent design system (e.g., ensuring all buttons use the exact same primary color).


B. Dynamic Theming (Dark Mode)

Variables are the easiest way to implement dynamic themes like Dark Mode. You simply redefine the color variables inside a Media Query or a class selector.

styles.css
/* Base Light Theme Colors */
:root {
--background-color: white;
--text-color: black;
}

/* Dark Theme Override */
@media (prefers-color-scheme: dark) {
:root {
--background-color: #1a1a1a;
--text-color: #f0f0f0;
}
}

Now, all elements that use var(--background-color) will automatically flip themes based on the user's OS preference.

C. Calculations

You can use Custom Properties within the calc() function.

styles.css
:root {
--sidebar-width: 250px;
}

.main-content {
/* Dynamically calculate the main content width */
width: calc(100% - var(--sidebar-width) - 2rem);
}

4. Interaction with JavaScript

Unlike preprocessor variables (like Sass variables), CSS Custom Properties are part of the live DOM and can be read and written directly with JavaScript, enabling powerful dynamic interactions.

4.1. Reading a Variable

script.js
// Get the value of the primary color on the root element
const root = document.documentElement;
const primaryColor = getComputedStyle(root).getPropertyValue('--primary-color');
console.log(primaryColor); // Output: #3B82F6

4.2. Setting a Variable

You can update a variable on any element, and all dependent styles will update instantly.

script.js
// Change the primary color to green dynamically
document.documentElement.style.setProperty('--primary-color', '#4CAF50');

Interactive CSS Variables Demo

This example demonstrates how changing a single variable value can update multiple dependent styles at once, and how a local variable overrides a global one.

In this demo, you can see how changing the value of --theme-color in the global scope affects the first box, while the second box uses its own local definition.