Skip to content
⚠️ This article was written in 2018. Some content may be outdated.

CSS Custom Properties (Variables) Practical Guide

CSS custom properties (also called CSS variables) are different from Sass/Less variables in a fundamental way: they're resolved at runtime in the browser, not at compile time. This enables things that preprocessors simply cannot do.

Basic Syntax

css
/* Definition: must start with -- */
:root {
  --primary-color: #4285f4;
  --spacing-md: 16px;
  --border-radius: 4px;
}

/* Usage: var() function */
.button {
  background-color: var(--primary-color);
  padding: var(--spacing-md);
  border-radius: var(--border-radius);
}

/* Fallback value */
.text {
  color: var(--text-color, #333); /* use #333 if --text-color is undefined */
}

The Key Difference from Sass Variables

scss
// Sass: compile-time variable — becomes a fixed value in the CSS output
$primary: #4285f4;
.button {
  background: $primary;
} // → .button { background: #4285f4; }

// After compilation, you can't dynamically change it in the browser
javascript
// CSS variable: live in the browser, changeable at runtime
document.documentElement.style.setProperty("--primary-color", "#ff5722");
// Every element using var(--primary-color) updates instantly!

Dark Mode Theme Switching

css
:root {
  --bg-color: #ffffff;
  --text-color: #333333;
  --card-bg: #f5f5f5;
}

[data-theme="dark"] {
  --bg-color: #1a1a1a;
  --text-color: #e0e0e0;
  --card-bg: #2d2d2d;
}

body {
  background-color: var(--bg-color);
  color: var(--text-color);
}
.card {
  background: var(--card-bg);
}
javascript
// Toggle theme
function toggleTheme() {
  const current = document.documentElement.getAttribute("data-theme");
  document.documentElement.setAttribute(
    "data-theme",
    current === "dark" ? "light" : "dark",
  );
}

No JavaScript is needed to restyle every element. Just change one attribute and the entire page updates through CSS variables.

Component-Level Variables

CSS variables can be scoped to a component, not just the global :root:

css
/* Component defaults */
.card {
  --card-padding: 16px;
  --card-radius: 8px;
  --card-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);

  padding: var(--card-padding);
  border-radius: var(--card-radius);
  box-shadow: var(--card-shadow);
}

/* Override at use site */
.product-card {
  --card-padding: 24px;
  --card-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}

Combining with Sass

scss
// Use Sass for compile-time constants (design tokens)
$colors: (
  "primary": #4285f4,
  "danger": #f44336,
);

// Generate CSS variables from Sass map
:root {
  @each $name, $value in $colors {
    --color-#{$name}: #{$value};
  }
}

// Use CSS variables in components (now runtime-changeable)
.button {
  background: var(--color-primary);
}

Reading/Writing from JavaScript

javascript
// Read a CSS variable
const style = getComputedStyle(document.documentElement);
const primaryColor = style.getPropertyValue("--primary-color").trim();

// Write (affects all elements inheriting this variable)
document.documentElement.style.setProperty("--primary-color", "#ff5722");

// Write on a specific element (scoped)
element.style.setProperty("--card-bg", "#f0f0f0");

MIT Licensed