CSS Custom Properties (CSS Variables) Complete Guide

Published April 2026 · CSS Guide · Try CSS Minifier →

CSS custom properties, commonly called CSS variables, are one of the most impactful features added to CSS in recent years. They let you store reusable values in your stylesheets, create dynamic themes that respond to user preferences, and build design systems that are easy to maintain and scale. Unlike preprocessor variables (Sass, Less), CSS custom properties are live in the browser — they can be changed at runtime using JavaScript and cascade through the DOM just like regular CSS properties.

This guide covers everything from basic syntax to advanced theming patterns and real-world best practices.

Declaring CSS Custom Properties

Custom properties are declared with a double-hyphen prefix (--) and follow the same rules as regular CSS properties. They can be declared on any selector.

/* Global variables (accessible everywhere) */
:root {
  --primary-color: #2563eb;
  --secondary-color: #7c3aed;
  --text-color: #1f2937;
  --bg-color: #ffffff;
  --font-size-base: 16px;
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  --border-radius: 8px;
  --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}

/* Component-scoped variables */
.card {
  --card-padding: 24px;
  --card-bg: #ffffff;
}

/* Theme-scoped variables */
.dark-theme {
  --primary-color: #60a5fa;
  --text-color: #e5e7eb;
  --bg-color: #111827;
}

The :root pseudo-class represents the document's root element and is the conventional place to declare global variables. Variables declared on :root are accessible to every element on the page.

Using Custom Properties with var()

Reference custom properties using the var() function:

.button {
  background-color: var(--primary-color);
  color: var(--bg-color);
  font-size: var(--font-size-base);
  padding: var(--spacing-sm) var(--spacing-md);
  border-radius: var(--border-radius);
  box-shadow: var(--shadow);
}

.card {
  padding: var(--card-padding);
  background: var(--card-bg);
}

You can use custom properties in any CSS property value. They can even reference other custom properties:

:root {
  --primary: #2563eb;
  --primary-light: #60a5fa;
  --primary-dark: #1d4ed8;
  --gradient: linear-gradient(135deg, var(--primary), var(--primary-dark));
}

.hero {
  background: var(--gradient);
}

Fallback Values

The var() function accepts an optional second argument — a fallback value used when the custom property is not defined or is invalid.

/* Basic fallback */
.element {
  color: var(--text-color, #333);
}

/* Fallback with comma (note the parentheses trick) */
.element {
  font-family: var(--font-stack, 'Segoe UI', sans-serif);
  /* The fallback itself can contain commas */
}

/* Nested var() fallback */
.element {
  background: var(--card-bg, var(--bg-color, white));
}

Fallbacks are useful when building reusable components that might be used in different contexts with or without certain variables defined.

Scoping and Cascading

CSS custom properties follow standard CSS cascade rules. A variable declared on a child element overrides the same variable declared on a parent.

:root {
  --accent: blue;
}

.card {
  --accent: green; /* Scoped to .card and its children */
}

.card .title {
  color: var(--accent); /* green — uses the nearest defined value */
}

.other {
  color: var(--accent); /* blue — falls back to :root */
}

This cascading behavior is what makes theming possible. By redefining variables on a parent element, all descendants automatically use the new values.

Building a Theme System

CSS variables make dark mode and theme switching trivially simple. Define your theme as a set of variable overrides on a class, then toggle the class.

:root {
  --bg-primary: #ffffff;
  --bg-secondary: #f3f4f6;
  --text-primary: #111827;
  --text-secondary: #6b7280;
  --border-color: #e5e7eb;
  --shadow-color: rgba(0, 0, 0, 0.1);
}

[data-theme="dark"] {
  --bg-primary: #111827;
  --bg-secondary: #1f2937;
  --text-primary: #f9fafb;
  --text-secondary: #9ca3af;
  --border-color: #374151;
  --shadow-color: rgba(0, 0, 0, 0.3);
}

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

Toggle the theme with JavaScript:

function setTheme(theme) {
  document.documentElement.setAttribute('data-theme', theme);
  localStorage.setItem('theme', theme);
}

// Initialize from saved preference
const saved = localStorage.getItem('theme');
if (saved) {
  document.documentElement.setAttribute('data-theme', saved);
}

JavaScript Manipulation

One of the most powerful features of CSS custom properties is that they can be read and modified at runtime with JavaScript. This enables dynamic styling without touching the stylesheet.

const element = document.querySelector('.element');

// Set a custom property
element.style.setProperty('--primary-color', '#ef4444');

// Read a custom property
const value = getComputedStyle(element).getPropertyValue('--primary-color');
console.log(value); // #ef4444

// Remove a custom property
element.style.removeProperty('--primary-color');

Dynamic Color Picker Example

const colorInput = document.querySelector('#color-picker');

colorInput.addEventListener('input', (e) => {
  document.documentElement.style.setProperty('--primary-color', e.target.value);
});

This lets users customize the theme in real time. The color picker value is written to a CSS variable, and all elements using that variable update instantly.

Scroll-Driven Animations with Variables

window.addEventListener('scroll', () => {
  const scrollY = window.scrollY;
  const maxScroll = document.body.scrollHeight - window.innerHeight;
  const progress = scrollY / maxScroll;

  document.documentElement.style.setProperty('--scroll-progress', progress);
});
.progress-bar {
  width: calc(var(--scroll-progress, 0) * 100%);
  background: var(--primary-color);
  height: 3px;
  position: fixed;
  top: 0;
}

Responsive Design with CSS Variables

Custom properties can be redefined inside media queries to create responsive design systems:

:root {
  --container-width: 1200px;
  --font-size-base: 16px;
  --grid-columns: 3;
  --spacing: 16px;
}

@media (max-width: 768px) {
  :root {
    --container-width: 100%;
    --font-size-base: 14px;
    --grid-columns: 2;
    --spacing: 12px;
  }
}

@media (max-width: 480px) {
  :root {
    --grid-columns: 1;
    --spacing: 8px;
  }
}

.grid {
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), 1fr);
  gap: var(--spacing);
  max-width: var(--container-width);
  margin: 0 auto;
  font-size: var(--font-size-base);
}

This approach centralizes all responsive breakpoints in one place. Instead of overriding individual properties across dozens of selectors, you redefine the variables once.

Calculations with calc()

CSS variables work seamlessly with calc() for dynamic computations:

:root {
  --sidebar-width: 280px;
  --header-height: 64px;
  --content-padding: 24px;
}

.main-content {
  width: calc(100% - var(--sidebar-width));
  padding: var(--content-padding);
  min-height: calc(100vh - var(--header-height));
}

Best Practices

Frequently Asked Questions

What is the difference between CSS variables and Sass variables?

Sass variables are compiled away at build time and cannot be changed at runtime. CSS custom properties are native to the browser, can be scoped to any element, and can be manipulated with JavaScript at runtime. They serve different purposes and can coexist.

Can I use CSS variables in media queries?

Modern browsers now support CSS variables in media query conditions. Previously, you could only use them inside the media query blocks, not in the condition itself.

How do I set a fallback value for CSS variables?

Use the second argument of var(): var(--color, #333). If --color is not defined, #333 will be used instead.

Are CSS variables case-sensitive?

Yes, CSS custom properties are case-sensitive. --primary-color and --Primary-Color are two different variables.

Can CSS variables be animated?

CSS custom properties can be registered with @property to define a type and make them animatable. Without registration, they are treated as strings and cannot be smoothly transitioned.