CSS Custom Properties: Complete Guide to CSS Variables and Dynamic Styling

June 16, 2025

CSS Custom Properties, commonly known as CSS Variables, revolutionize how we write and maintain stylesheets by introducing dynamic values that can be reused throughout your CSS. Unlike preprocessor variables (Sass, Less), CSS custom properties are native to the browser and can be manipulated at runtime using JavaScript, making them incredibly powerful for creating responsive, themeable, and maintainable web applications.

What Are CSS Custom Properties?

CSS Custom Properties are entities defined by CSS authors that contain specific values to be reused throughout a document. They’re defined using the custom property notation (two dashes followed by a name) and accessed using the var() function. Unlike traditional CSS properties, custom properties cascade and can be inherited, making them truly dynamic.

Key Benefits:

  • Runtime Manipulation: Change values dynamically with JavaScript
  • Cascade and Inheritance: Follow CSS’s natural flow
  • Better Maintainability: Centralize commonly used values
  • Theme Implementation: Easy dark/light mode switching
  • Responsive Design: Different values at different breakpoints

Basic Syntax and Declaration

CSS custom properties are declared using two dashes (–) followed by the property name. They can be defined at any scope but are commonly declared in the :root pseudo-class for global access.

/* Global custom properties */
:root {
  --primary-color: #3498db;
  --secondary-color: #2ecc71;
  --font-size-base: 16px;
  --spacing-unit: 8px;
  --border-radius: 4px;
}

/* Using custom properties */
.button {
  background-color: var(--primary-color);
  font-size: var(--font-size-base);
  padding: calc(var(--spacing-unit) * 2);
  border-radius: var(--border-radius);
}

Interactive Example: Basic Custom Properties

Dynamic Card

Hover to see the effect

View CSS Code
.demo-basic {
  –demo-primary: #3498db;
  –demo-secondary: #2ecc71;
  –demo-spacing: 12px;
}

.demo-card {
  background: linear-gradient(135deg, var(–demo-primary), var(–demo-secondary));
  padding: var(–demo-spacing);
  border-radius: var(–demo-spacing);
}

Understanding Scope and Inheritance

CSS custom properties follow the cascade and inheritance rules of CSS. When declared at different scopes, they create a hierarchy where more specific selectors override more general ones.

/* Global scope */
:root {
  --text-color: #333;
  --bg-color: #fff;
}

/* Component scope */
.dark-theme {
  --text-color: #fff;
  --bg-color: #333;
}

/* Element scope */
.special-button {
  --text-color: #ff6b6b;
}

/* All elements inherit these values */
.content {
  color: var(--text-color);
  background-color: var(--bg-color);
}

Scope Demonstration

Default Theme

This uses the default color scheme.

Nested Element

This overrides the text color but inherits background.

Dark Theme

This uses the dark color scheme.

Nested Element

This overrides the text color but inherits background.

Fallback Values and Error Handling

The var() function accepts a second parameter as a fallback value, which is used when the custom property is undefined or invalid. This provides graceful degradation and error handling.

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

/* Multiple fallbacks (nested) */
.element {
  font-size: var(--large-text, var(--medium-text, 16px));
}

/* Fallback with calculations */
.element {
  margin: var(--custom-margin, calc(1rem + 10px));
}

/* Complex fallback values */
.element {
  background: var(--gradient, linear-gradient(45deg, #ff6b6b, #4ecdc4));
}

Fallback Values Example

Defined Variable: Uses –demo-fallback-color (#3498db)
Fallback Used: Variable undefined, using gradient fallback
Multiple Fallbacks: Nested fallbacks with calculations

Dynamic Theming with CSS Custom Properties

One of the most powerful applications of CSS custom properties is creating dynamic themes. You can switch entire color schemes by changing just a few variables at the root level.

Complete Theme System Example

Dynamic Theme System

This entire component changes theme with CSS custom properties.



Color Variables in Action
  • Background: var(–theme-bg)
  • Surface: var(–theme-surface)
  • Text: var(–theme-text)
  • Primary: var(–theme-primary)

Mathematical Operations with Custom Properties

CSS custom properties work seamlessly with CSS functions like calc(), enabling mathematical operations and dynamic calculations. This makes them incredibly powerful for responsive design and spacing systems.

:root {
  --base-spacing: 8px;
  --multiplier: 2;
  --container-width: 1200px;
  --sidebar-ratio: 0.25;
}

.spacing-system {
  /* Calculated spacing values */
  --space-xs: calc(var(--base-spacing) * 0.5);  /* 4px */
  --space-sm: var(--base-spacing);              /* 8px */
  --space-md: calc(var(--base-spacing) * 2);    /* 16px */
  --space-lg: calc(var(--base-spacing) * 3);    /* 24px */
  --space-xl: calc(var(--base-spacing) * 4);    /* 32px */
}

.layout {
  --main-width: calc(var(--container-width) * (1 - var(--sidebar-ratio)));
  --sidebar-width: calc(var(--container-width) * var(--sidebar-ratio));
}

.responsive-grid {
  /* Dynamic grid calculations */
  --columns: 3;
  --gap: var(--space-md);
  --item-width: calc((100% - (var(--gap) * (var(--columns) - 1))) / var(--columns));
}

Mathematical Operations Demo

Dynamic Controls



10px


3

Calculated Spacing Scale

0.5x
1x
1.5x
2x
3x

Dynamic Grid Layout

Item 1
Item 2
Item 3
Item 4
Item 5
Item 6

Responsive Design with Custom Properties

CSS custom properties can be redefined within media queries, making them perfect for responsive design. Instead of changing multiple properties across different selectors, you can modify just the custom property values.

:root {
  --container-padding: 16px;
  --font-size-h1: 2rem;
  --grid-columns: 1;
  --card-gap: 1rem;
}

@media (min-width: 768px) {
  :root {
    --container-padding: 24px;
    --font-size-h1: 2.5rem;
    --grid-columns: 2;
    --card-gap: 1.5rem;
  }
}

@media (min-width: 1024px) {
  :root {
    --container-padding: 32px;
    --font-size-h1: 3rem;
    --grid-columns: 3;
    --card-gap: 2rem;
  }
}

/* Components automatically adapt */
.container {
  padding: var(--container-padding);
}

.title {
  font-size: var(--font-size-h1);
}

.card-grid {
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), 1fr);
  gap: var(--card-gap);
}

Responsive Design Demo

Current Breakpoint: Mobile

Resize your browser to see the layout adapt!
Grid columns, gaps, padding, and font sizes all change based on screen size using CSS custom properties.

Card 1
Card 2
Card 3
Card 4
Card 5
Card 6

JavaScript Integration and Dynamic Updates

One of the most powerful features of CSS custom properties is their ability to be manipulated in real-time using JavaScript. This enables dynamic styling based on user interactions, data changes, or external events.

// Reading custom property values
const rootStyles = getComputedStyle(document.documentElement);
const primaryColor = rootStyles.getPropertyValue('--primary-color');

// Setting custom property values
document.documentElement.style.setProperty('--primary-color', '#e74c3c');

// Setting on specific elements
const element = document.querySelector('.my-component');
element.style.setProperty('--local-color', '#2ecc71');

// Removing custom properties
document.documentElement.style.removeProperty('--temporary-color');

// Dynamic updates based on data
function updateProgress(percentage) {
  document.documentElement.style.setProperty('--progress-width', percentage + '%');
}

// Theme switching
function switchTheme(theme) {
  const themes = {
    light: { '--bg': '#fff', '--text': '#333' },
    dark: { '--bg': '#333', '--text': '#fff' }
  };
  
  Object.entries(themes[theme]).forEach(([property, value]) => {
    document.documentElement.style.setProperty(property, value);
  });
}

Interactive JavaScript Demo