Ad – 728×90
🔧 Advanced CSS

CSS Variables – Custom Properties Explained

CSS custom properties, commonly called CSS variables, let you store values in named containers and reuse them throughout your stylesheet. Defined once at the top, used everywhere — and when you change the variable, every element using it updates automatically. They unlock powerful patterns like runtime theming, dark mode, and component-level design tokens, all without JavaScript build tools.

⏱️ 18 min read 🎯 Intermediate 📅 Updated 2026 👁️ Lesson 1 of 5

Declaring CSS Variables

Custom property names must start with two dashes (--). They are case-sensitive. Declare them inside any CSS rule; the declaration is scoped to that selector.

CSS – Declaring variables
/* Global variables — declared on :root, available everywhere */
:root {
  --primary-color: #1572B6;
  --secondary-color: #e44d26;
  --font-size-base: 16px;
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 32px;
  --border-radius: 6px;
  --shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}

/* Local variable — scoped to .card and its children */
.card {
  --card-padding: 24px;
  padding: var(--card-padding);
}

Using Variables with var()

Read a variable anywhere in a property value using var(--name). The variable resolves to its declared value at computed time.

CSS – Using var()
button {
  background: var(--primary-color);
  color: #fff;
  padding: var(--spacing-sm) var(--spacing-md);
  border-radius: var(--border-radius);
  box-shadow: var(--shadow);
  font-size: var(--font-size-base);
}

h1 {
  color: var(--primary-color);
  margin-bottom: var(--spacing-lg);
}

a {
  color: var(--secondary-color);
}

Fallback Values

var() accepts an optional second argument — a fallback used when the variable is not defined. Fallbacks can themselves contain var() calls.

CSS – Fallback values
/* var(--name, fallback) */
.btn {
  color: var(--btn-color, #fff);          /* uses #fff if --btn-color not set */
  padding: var(--btn-padding, 10px 20px); /* multi-value fallback */
}

/* Chained fallbacks */
h2 {
  font-size: var(--heading-lg, var(--font-size-base, 16px));
  /* tries --heading-lg, then --font-size-base, then 16px */
}

Scope and Inheritance

CSS variables follow the same cascade and inheritance rules as other properties. A child element can access any variable declared on an ancestor — including :root. Redeclaring a variable in a nested selector overrides it locally without affecting the rest of the document.

CSS – Scope
:root {
  --color: #1572B6;   /* global default */
}

.card {
  --color: #e44d26;   /* override locally for .card and children */
  background: var(--color);   /* #e44d26 */
}

.card .title {
  color: var(--color);   /* still #e44d26 — inherits from .card */
}

h1 {
  color: var(--color);   /* #1572B6 — uses :root value */
}
Ad – 336×280

Theming with CSS Variables

The most powerful use of custom properties is runtime theming. Define all design tokens on :root, then override them under a theme class or media query. No extra stylesheet download needed.

CSS – Dark mode theming
/* Light theme — default */
:root {
  --bg: #ffffff;
  --text: #1a1a1a;
  --surface: #f5f5f5;
  --primary: #1572B6;
  --border: #e0e0e0;
}

/* Dark theme — toggled via class or prefers-color-scheme */
[data-theme="dark"],
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #121212;
    --text: #f1f1f1;
    --surface: #1e1e1e;
    --primary: #4fc3f7;
    --border: #333333;
  }
}

/* Components just use the tokens — no change needed */
body       { background: var(--bg); color: var(--text); }
.card      { background: var(--surface); border: 1px solid var(--border); }
a, .btn    { color: var(--primary); }

Updating Variables with JavaScript

JavaScript can read and write CSS custom properties at runtime, enabling dynamic theming or user-controlled styles without rewriting CSS classes.

JS – Updating CSS variables
const root = document.documentElement;  // or any element

// Read a variable
const primary = getComputedStyle(root).getPropertyValue('--primary-color').trim();
console.log(primary); // e.g. "#1572B6"

// Set a variable
root.style.setProperty('--primary-color', '#e44d26');

// Remove / revert to stylesheet value
root.style.removeProperty('--primary-color');

// Practical: theme switcher
document.querySelector('#themeToggle').addEventListener('click', () => {
  const current = root.getAttribute('data-theme');
  root.setAttribute('data-theme', current === 'dark' ? 'light' : 'dark');
});

// Practical: user-controlled font size slider
document.querySelector('#fontSlider').addEventListener('input', (e) => {
  root.style.setProperty('--font-size-base', e.target.value + 'px');
});

Design Tokens Pattern

Large projects organize CSS variables as design tokens — a single source of truth for every visual value: spacing, color, typography, shadow, radius. Components reference tokens rather than raw values, making global restyling a one-line change.

CSS – Design token system
:root {
  /* Primitive tokens */
  --color-blue-500: #1572B6;
  --color-blue-600: #0f5fa0;
  --color-red-500:  #e44d26;
  --space-1: 4px;
  --space-2: 8px;
  --space-4: 16px;
  --space-8: 32px;
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-full: 9999px;

  /* Semantic tokens — reference primitives */
  --color-primary:        var(--color-blue-500);
  --color-primary-hover:  var(--color-blue-600);
  --color-danger:         var(--color-red-500);
  --spacing-component:    var(--space-4);
  --border-radius-button: var(--radius-sm);
}

/* Now components use semantic tokens — easy to retheme */
.btn-primary {
  background: var(--color-primary);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--border-radius-button);
}
.btn-primary:hover {
  background: var(--color-primary-hover);
}

📋 Summary

  • Declare with --name: value; inside any selector. Use :root for global variables.
  • Read with var(--name) in any property value.
  • var(--name, fallback) — second argument is the fallback when variable is undefined.
  • Variables are scoped to their selector and inherited by children. Redeclare in a nested scope to override locally.
  • Dark mode theming: define tokens on :root, override under [data-theme="dark"] or prefers-color-scheme: dark.
  • JavaScript: getPropertyValue() to read, setProperty() to write, removeProperty() to revert.
  • Design tokens pattern: primitive tokens → semantic tokens → component styles. One change, global effect.

Frequently Asked Questions

What is the difference between CSS variables and preprocessor variables (Sass/Less)? +

Sass/Less variables are compile-time — they are resolved once during the build step and replaced with their values in the output CSS. The browser never sees the variable names. CSS custom properties are runtime — the browser keeps the variable names and their values, resolves them during rendering, and you can change them with JavaScript or media queries without re-compiling anything. CSS variables also cascade and inherit like normal properties; Sass variables do not.

Can I use CSS variables inside calc()? +

Yes — calc(var(--spacing-md) * 2) works perfectly. The variable is substituted first, then calc() evaluates. This is extremely useful for spacing systems: declare a base unit like --space: 8px and compute all spacing from it: padding: calc(var(--space) * 2).

Are CSS variables supported in all browsers? +

Yes — CSS custom properties have been supported in all major browsers since 2016 (Chrome 49, Firefox 31, Safari 9.1, Edge 15). Global support is above 96% as of 2026. The only holdout was Internet Explorer, which is now end-of-life. You can use CSS variables without any polyfill in modern production projects.

Can a CSS variable reference another CSS variable? +

Yes — --semantic: var(--primitive); creates a chain. The browser resolves variable references lazily, so a variable can reference another variable declared later in the same rule or declared on the same element via a different rule. Circular references (A references B, B references A) produce an invalid value and the property falls back to its initial value.