CSS April 2026 · 10 min read

CSS Custom Properties: The Complete Guide

CSS custom properties — commonly called CSS variables — are one of the most impactful features added to CSS in the last decade. They enable dynamic theming, design token systems, and runtime style changes that were previously only possible with JavaScript or preprocessors like Sass.

The Basic Syntax

Custom properties are defined with a double-dash prefix and accessed with the var() function:

:root {
  --color-primary: #7c5cfc;
  --spacing-base: 1rem;
  --border-radius: 12px;
}

.button {
  background: var(--color-primary);
  padding: var(--spacing-base);
  border-radius: var(--border-radius);
}

Scope and Inheritance

Unlike Sass variables, CSS custom properties are live in the DOM and follow CSS inheritance. A variable defined on a parent is available to all its descendants. This makes component-level scoping natural:

:root {
  --card-bg: rgba(255,255,255,0.03); /* global default */
}

.card--featured {
  --card-bg: rgba(124,92,252,0.08); /* scoped override */
}

.card {
  background: var(--card-bg); /* uses nearest ancestor value */
}

This is fundamentally different from Sass variables, which are compile-time substitutions. CSS custom properties resolve at runtime, meaning JavaScript can update them and the UI responds immediately.

Fallback Values

The var() function accepts a fallback as the second argument — used when the variable is undefined or invalid:

.component {
  color: var(--text-color, #e2e8f0);
  /* Uses --text-color if defined, #e2e8f0 otherwise */

  /* Fallbacks can chain: */
  font-size: var(--font-size-lg, var(--font-size-base, 1rem));
}

Dark Mode with CSS Variables

Custom properties are the cleanest approach to dark mode — define your semantic color tokens once at the root, then swap their values based on color scheme preference:

:root {
  --bg-surface:    #ffffff;
  --bg-elevated:   #f8fafc;
  --text-primary:  #1e293b;
  --text-muted:    #64748b;
  --border-color:  #e2e8f0;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg-surface:    #050508;
    --bg-elevated:   #0f0f14;
    --text-primary:  #e2e8f0;
    --text-muted:    #94a3b8;
    --border-color:  rgba(255,255,255,0.08);
  }
}

/* Components use tokens, never raw colors */
.card {
  background: var(--bg-elevated);
  color: var(--text-primary);
  border: 1px solid var(--border-color);
}

Every component automatically adapts to dark mode because they reference semantic tokens, not hardcoded color values. This is the key advantage over hardcoding colors directly.

JavaScript Integration

CSS custom properties can be read and written from JavaScript at runtime — this is what makes them fundamentally different from preprocessor variables:

// Read a CSS variable
const root = document.documentElement;
const primary = getComputedStyle(root).getPropertyValue('--color-primary');

// Write a CSS variable (triggers re-render)
root.style.setProperty('--color-primary', '#ec4899');

// Component-level override
const card = document.querySelector('.card');
card.style.setProperty('--card-accent', '#0ea5e9');

Design Token System

The real power of CSS custom properties is as a design token system. Define all your design decisions as variables in one place:

:root {
  /* ─── Color Palette ─────────────────────── */
  --hue-primary: 257;
  --color-primary:     hsl(var(--hue-primary) 95% 66%);
  --color-primary-dim: hsl(var(--hue-primary) 95% 66% / 0.15);

  /* ─── Spacing (8px grid) ───────────────── */
  --space-1: 0.5rem;   /* 8px  */
  --space-2: 1rem;     /* 16px */
  --space-3: 1.5rem;   /* 24px */
  --space-4: 2rem;     /* 32px */
  --space-6: 3rem;     /* 48px */

  /* ─── Typography ───────────────────────── */
  --font-size-sm:   0.875rem;
  --font-size-base: 1rem;
  --font-size-lg:   1.125rem;
  --font-size-xl:   1.25rem;
  --font-size-2xl:  1.5rem;
  --line-height-body:    1.6;
  --line-height-heading: 1.2;

  /* ─── Borders ───────────────────────────── */
  --radius-sm:  6px;
  --radius-md:  12px;
  --radius-lg:  20px;
  --radius-full: 9999px;

  /* ─── Shadows ───────────────────────────── */
  --shadow-sm: 0 1px 3px rgba(0,0,0,0.2), 0 4px 8px rgba(0,0,0,0.15);
  --shadow-md: 0 4px 8px rgba(0,0,0,0.25), 0 16px 32px rgba(0,0,0,0.2);
}

CSS Variables vs Sass Variables

Both solve the "don't repeat yourself" problem in CSS, but they work differently and solve different problems:

Common Mistakes

CSS variables have a few non-obvious gotchas:

Frequently Asked Questions

Related Guides

palette

Gradient Generator

Use CSS variables with gradients

contrast

Contrast Checker

Test your token color combinations