CSS var() -- CSS VARIABLES • CUSTOM PROPERTIES • THEMING
Frontend

CSS Variables: Dynamic Styling Made Easy

Mayur Dabhi
Mayur Dabhi
March 30, 2026
18 min read

CSS Variables, officially known as CSS Custom Properties, have revolutionized how we write and maintain stylesheets. Gone are the days of find-and-replace across massive CSS files when updating a color scheme. With CSS Variables, you can define values once and reuse them throughout your entire stylesheet—and even change them dynamically with JavaScript.

This comprehensive guide covers everything from basic syntax to advanced patterns, including theming systems, responsive design, animations, and JavaScript integration. By the end, you'll have the knowledge to build maintainable, dynamic styling systems that scale with your projects.

What You'll Learn
  • CSS Variable syntax and declaration
  • Scoping, inheritance, and the cascade
  • Building theme systems (dark/light mode)
  • Dynamic animations with CSS Variables
  • JavaScript integration and live updates
  • Real-world patterns and best practices
  • Browser support and fallback strategies

Understanding CSS Variables Syntax

CSS Variables use a simple yet powerful syntax. They're defined with a double hyphen prefix (--) and accessed using the var() function. Let's start with the fundamentals.

CSS Variable Syntax Breakdown Declaration (Defining) --primary-color: #264de4; ↑ name value ↑ Usage (Consuming) color: var(--primary-color); var() ↑

CSS Variables are declared with -- prefix and consumed using var() function

Basic Declaration and Usage

CSS Variables are typically declared in the :root pseudo-class to make them globally available. However, they can be declared in any selector for scoped usage.

styles.css
/* Global variables - available everywhere */
:root {
  /* Colors */
  --primary-color: #264de4;
  --secondary-color: #9b59b6;
  --background: #ffffff;
  --text-color: #1a1a1a;
  
  /* Spacing */
  --spacing-xs: 4px;
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  --spacing-xl: 32px;
  
  /* Typography */
  --font-family: 'Inter', sans-serif;
  --font-size-base: 16px;
  --line-height: 1.6;
  
  /* Borders & Shadows */
  --border-radius: 8px;
  --shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

/* Using the variables */
.button {
  background: var(--primary-color);
  color: white;
  padding: var(--spacing-sm) var(--spacing-md);
  border-radius: var(--border-radius);
  font-family: var(--font-family);
  box-shadow: var(--shadow);
}

.card {
  background: var(--background);
  color: var(--text-color);
  padding: var(--spacing-lg);
  border-radius: var(--border-radius);
}

Fallback Values

The var() function accepts a second parameter as a fallback value. This is crucial for defensive CSS and handling undefined variables.

fallbacks.css
.element {
  /* Simple fallback */
  color: var(--undefined-var, #333);
  
  /* Fallback to another variable */
  background: var(--accent-color, var(--primary-color, blue));
  
  /* Fallback with complex value */
  box-shadow: var(--custom-shadow, 0 4px 12px rgba(0,0,0,0.15));
}
Pro Tip

Always provide fallback values for critical styles, especially when variables might be overridden or when supporting older browsers that don't fully support CSS Variables.

Scoping and Inheritance

One of the most powerful features of CSS Variables is their participation in the CSS cascade. Variables can be scoped to specific elements and will inherit down the DOM tree.

CSS Variable Inheritance & Scoping :root --primary: #264de4 .theme-light (inherits) --primary: #264de4 ✓ --bg: #ffffff (new) .theme-dark (overrides) --primary: #6c8aff ✗ --bg: #000000 (new) .card (child) uses --primary → #264de4 .card (child) uses --primary → #6c8aff Variables cascade down • Children inherit parent values

CSS Variables follow the cascade and can be overridden at any level

scoping.css
/* Global scope */
:root {
  --primary: #264de4;
  --text: #1a1a1a;
}

/* Component-scoped override */
.danger-zone {
  --primary: #e74c3c; /* Red for this section */
}

/* All buttons use --primary */
.button {
  background: var(--primary);
}

/* Button in .danger-zone will be red! */

Building a Theme System

CSS Variables shine when building theme systems. Here's how to implement a robust dark/light mode toggle that's maintainable and performant.

/* Define theme variables */
[data-theme="light"] {
  --bg-primary: #ffffff;
  --bg-secondary: #f8f9fa;
  --text-primary: #1a1a1a;
  --text-muted: #666666;
  --border-color: #e0e0e0;
  --shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

[data-theme="dark"] {
  --bg-primary: #000000;
  --bg-secondary: #111111;
  --text-primary: #ffffff;
  --text-muted: #888888;
  --border-color: #2a2a2a;
  --shadow: 0 2px 8px rgba(255, 255, 255, 0.05);
}

/* Use variables throughout */
body {
  background: var(--bg-primary);
  color: var(--text-primary);
  transition: background 0.3s, color 0.3s;
}

.card {
  background: var(--bg-secondary);
  border: 1px solid var(--border-color);
  box-shadow: var(--shadow);
}
<!-- Set default theme on html element -->
<html lang="en" data-theme="dark">
<head>
  <!-- Prevent flash by setting theme early -->
  <script>
    const theme = localStorage.getItem('theme') || 
      (window.matchMedia('(prefers-color-scheme: dark)').matches 
        ? 'dark' : 'light');
    document.documentElement.dataset.theme = theme;
  </script>
</head>
<body>
  <button id="themeToggle">
    Toggle Theme
  </button>
</body>
</html>
const themeToggle = document.getElementById('themeToggle');

// Toggle theme function
function toggleTheme() {
  const html = document.documentElement;
  const currentTheme = html.dataset.theme;
  const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
  
  html.dataset.theme = newTheme;
  localStorage.setItem('theme', newTheme);
}

themeToggle.addEventListener('click', toggleTheme);

// Listen for system preference changes
window.matchMedia('(prefers-color-scheme: dark)')
  .addEventListener('change', (e) => {
    if (!localStorage.getItem('theme')) {
      document.documentElement.dataset.theme = 
        e.matches ? 'dark' : 'light';
    }
  });
Avoid Flash of Wrong Theme

Always set the theme in a blocking <script> in the <head> before your CSS loads. This prevents users from seeing a flash of the wrong theme on page load.

Dynamic Animations

CSS Variables can be animated directly with CSS (in supported browsers) or manipulated via JavaScript for smooth dynamic effects.

animations.css
/* Animatable custom properties (with @property) */
@property --gradient-angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

.gradient-border {
  --gradient-angle: 0deg;
  background: linear-gradient(
    var(--gradient-angle),
    #264de4,
    #9b59b6,
    #2ecc71
  );
  animation: rotate-gradient 3s linear infinite;
}

@keyframes rotate-gradient {
  to {
    --gradient-angle: 360deg;
  }
}

/* Progress bar with variable */
.progress-bar {
  --progress: 0%;
  width: var(--progress);
  transition: width 0.5s ease-out;
}

/* Hover effect with variable */
.card {
  --lift: 0;
  transform: translateY(calc(var(--lift) * -8px));
  box-shadow: 0 calc(var(--lift) * 10px) calc(var(--lift) * 30px) rgba(0,0,0,0.1);
  transition: transform 0.3s, box-shadow 0.3s;
}

.card:hover {
  --lift: 1;
}

JavaScript Integration

CSS Variables can be read and written from JavaScript, enabling powerful dynamic styling without class toggling or inline styles.

Reading Variables

Use getComputedStyle() to read the current value of any CSS variable from any element.

Writing Variables

Use element.style.setProperty() to dynamically set CSS variable values from JavaScript.

dynamic-styling.js
// Reading CSS Variables
const root = document.documentElement;
const styles = getComputedStyle(root);
const primaryColor = styles.getPropertyValue('--primary-color').trim();
console.log(primaryColor); // "#264de4"

// Writing CSS Variables
root.style.setProperty('--primary-color', '#e74c3c');

// Dynamic mouse-following gradient
document.addEventListener('mousemove', (e) => {
  const x = (e.clientX / window.innerWidth) * 100;
  const y = (e.clientY / window.innerHeight) * 100;
  
  root.style.setProperty('--mouse-x', `${x}%`);
  root.style.setProperty('--mouse-y', `${y}%`);
});

// Use in CSS:
// background: radial-gradient(
//   circle at var(--mouse-x) var(--mouse-y),
//   var(--accent), transparent
// );

// Progress bar update
function updateProgress(percent) {
  const progressBar = document.querySelector('.progress-bar');
  progressBar.style.setProperty('--progress', `${percent}%`);
}

// Color picker integration
const colorPicker = document.querySelector('input[type="color"]');
colorPicker.addEventListener('input', (e) => {
  root.style.setProperty('--user-accent', e.target.value);
});

Responsive Design with Variables

CSS Variables work beautifully with media queries, allowing you to adjust spacing, typography, and layout values at different breakpoints.

responsive-variables.css
/* Base (mobile-first) values */
:root {
  --container-width: 100%;
  --container-padding: 16px;
  --font-size-h1: 2rem;
  --font-size-body: 1rem;
  --grid-columns: 1;
  --spacing-section: 40px;
}

/* Tablet */
@media (min-width: 768px) {
  :root {
    --container-padding: 24px;
    --font-size-h1: 2.5rem;
    --grid-columns: 2;
    --spacing-section: 60px;
  }
}

/* Desktop */
@media (min-width: 1200px) {
  :root {
    --container-width: 1200px;
    --container-padding: 0;
    --font-size-h1: 3.5rem;
    --font-size-body: 1.1rem;
    --grid-columns: 3;
    --spacing-section: 100px;
  }
}

/* Usage - no media queries needed in components! */
.container {
  max-width: var(--container-width);
  padding: 0 var(--container-padding);
  margin: 0 auto;
}

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

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

section {
  padding: var(--spacing-section) 0;
}

CSS Variables vs Preprocessor Variables

How do CSS Custom Properties compare to Sass/LESS variables? Here's a detailed comparison:

Feature CSS Variables Sass/LESS Variables
Runtime Changes ✅ Yes - can change via JS ❌ No - compiled at build time
Cascade & Inheritance ✅ Full CSS cascade support ❌ No cascade awareness
Scoping ✅ DOM-aware scoping ✅ File/block scoping
Media Query Changes ✅ Supported natively ❌ Requires duplication
Calculations ✅ With calc() ✅ Native math operators
Browser Support ⚠️ IE11 not supported ✅ Compiles to standard CSS
DevTools Inspection ✅ Visible in browser ❌ Variables not visible
Best Practice

Use both! Sass variables for build-time constants (like breakpoints in mixins), and CSS Variables for runtime theming and dynamic values. They complement each other perfectly.

Real-World Patterns

Let's explore some practical patterns you'll use in real projects.

Component Design Tokens

/* Global tokens */
:root {
  /* Primitives */
  --color-blue-500: #264de4;
  --color-gray-100: #f8f9fa;
  --radius-sm: 4px;
  --radius-md: 8px;
  
  /* Semantic tokens */
  --color-primary: var(--color-blue-500);
  --color-surface: var(--color-gray-100);
}

/* Component tokens */
.button {
  --button-bg: var(--color-primary);
  --button-radius: var(--radius-md);
  --button-padding: 12px 24px;
  
  background: var(--button-bg);
  border-radius: var(--button-radius);
  padding: var(--button-padding);
}

/* Variant override */
.button--small {
  --button-padding: 8px 16px;
  --button-radius: var(--radius-sm);
}

Fluid Typography

:root {
  /* Fluid scale using clamp() */
  --font-size-sm: clamp(0.875rem, 0.8rem + 0.25vw, 1rem);
  --font-size-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
  --font-size-lg: clamp(1.25rem, 1rem + 1vw, 1.5rem);
  --font-size-xl: clamp(1.5rem, 1rem + 2vw, 2.5rem);
  --font-size-2xl: clamp(2rem, 1rem + 4vw, 4rem);
}

h1 { font-size: var(--font-size-2xl); }
h2 { font-size: var(--font-size-xl); }
p  { font-size: var(--font-size-base); }

Color Manipulation with HSL

:root {
  /* Store HSL components separately */
  --primary-h: 227;
  --primary-s: 79%;
  --primary-l: 52%;
  
  /* Compose the color */
  --primary: hsl(
    var(--primary-h),
    var(--primary-s),
    var(--primary-l)
  );
  
  /* Create variants easily! */
  --primary-light: hsl(
    var(--primary-h),
    var(--primary-s),
    calc(var(--primary-l) + 15%)
  );
  
  --primary-dark: hsl(
    var(--primary-h),
    var(--primary-s),
    calc(var(--primary-l) - 15%)
  );
  
  /* Semi-transparent version */
  --primary-alpha: hsla(
    var(--primary-h),
    var(--primary-s),
    var(--primary-l),
    0.2
  );
}

Browser Support

CSS Variables have excellent browser support, covering 97%+ of global users. However, IE11 doesn't support them.

Browser Support for CSS Variables Chrome 49+ ✓ Since 2016 Firefox 31+ ✓ Since 2014 Safari 9.1+ ✓ Since 2016 Edge 15+ ✓ Since 2017 IE 11 Not Supported ✗ Use fallbacks 97.2% Global Support

Fallback Strategies

fallback-strategies.css
/* Strategy 1: Duplicate declarations */
.button {
  background: #264de4; /* Fallback */
  background: var(--primary);
}

/* Strategy 2: @supports query */
.card {
  background: #ffffff;
}

@supports (--css: variables) {
  .card {
    background: var(--surface);
  }
}

/* Strategy 3: CSS Variables fallback chain */
.element {
  color: var(--custom, var(--default, #333));
}

Best Practices Summary

1

Use Descriptive Names

Name variables by their purpose (--color-primary) not their value (--blue). This makes theming easier and code more readable.

2

Organize with Prefixes

Group related variables: --color-*, --spacing-*, --font-*. This improves discoverability and prevents naming collisions.

3

Define at :root for Globals

Use :root for theme-wide variables. Use component selectors for scoped variables that shouldn't leak globally.

4

Always Provide Fallbacks

Use the second parameter in var() for critical styles. This ensures graceful degradation if a variable is undefined.

5

Combine with Preprocessors

Use Sass for build-time logic and mixins, CSS Variables for runtime theming. They work great together!

Ready to Transform Your CSS?

CSS Variables are now a fundamental part of modern web development. Start using them today to build more maintainable, flexible, and dynamic stylesheets. Your future self (and your team) will thank you!

CSS Variables Theming Custom Properties Frontend
Mayur Dabhi

Mayur Dabhi

Full Stack Developer with 5+ years of experience specializing in Laravel, React, and modern web technologies. Passionate about clean code and sharing knowledge.