11 min read

CSS Variables with JavaScript: Dynamic Theming and Runtime Changes

11 min read
250 words
16 sections17 code blocks

Why Use CSS Variables with JavaScript?

CSS custom properties aren't just for static values in your stylesheet. Because they live in the browser (unlike preprocessor variables), you can read and modify them with JavaScript at runtime. This enables dynamic theming, user preferences, and interactive style changes.

Reading CSS Variables with JavaScript

Getting a Variable Value

JavaScript
// Get a CSS variable from the root element
const root = document.documentElement;
const primaryColor = getComputedStyle(root).getPropertyValue('--primary-color');
console.log(primaryColor); // "#3498db"

// Get from a specific element
const card = document.querySelector('.card');
const cardPadding = getComputedStyle(card).getPropertyValue('--card-padding');

Important Notes

  • getComputedStyle() returns the computed value after inheritance and cascading
  • The returned value includes any leading whitespace — use .trim() if needed
  • The variable must be defined on the element or an ancestor

Setting CSS Variables with JavaScript

Setting on Root (Global)

JavaScript
// Set a global CSS variable
document.documentElement.style.setProperty('--primary-color', '#e74c3c');
document.documentElement.style.setProperty('--font-size-base', '18px');
document.documentElement.style.setProperty('--spacing-md', '2rem');

Setting on Specific Elements

JavaScript
// Set a variable on a specific element
const card = document.querySelector('.card');
card.style.setProperty('--card-bg', '#f0f0f0');
card.style.setProperty('--card-shadow', '0 4px 12px rgba(0,0,0,0.15)');

Removing a Variable

JavaScript
document.documentElement.style.removeProperty('--primary-color');

Dark Mode Toggle

One of the most popular use cases — toggling between light and dark themes:

CSS
:root {
  --bg-color: #ffffff;
  --text-color: #333333;
  --card-bg: #f8f9fa;
  --border-color: #dee2e6;
  --primary: #3498db;
}

:root.dark-theme {
  --bg-color: #1a1a2e;
  --text-color: #e0e0e0;
  --card-bg: #16213e;
  --border-color: #2a2a4a;
  --primary: #5dade2;
}

body {
  background: var(--bg-color);
  color: var(--text-color);
  transition: background 0.3s, color 0.3s;
}

.card {
  background: var(--card-bg);
  border: 1px solid var(--border-color);
}
JavaScript
// Toggle dark mode
const toggleButton = document.querySelector('.theme-toggle');

toggleButton.addEventListener('click', () => {
  document.documentElement.classList.toggle('dark-theme');

  // Save preference
  const isDark = document.documentElement.classList.contains('dark-theme');
  localStorage.setItem('theme', isDark ? 'dark' : 'light');
});

// Load saved preference on page load
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark') {
  document.documentElement.classList.add('dark-theme');
}

Dynamic Color Picker

Let users choose their own accent color:

CSS
:root {
  --user-color: #3498db;
}

.btn-primary { background: var(--user-color); }
a { color: var(--user-color); }
.heading-accent { border-left: 4px solid var(--user-color); }
JavaScript
const colorPicker = document.querySelector('#color-picker');

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

Mouse-Tracking Effects

Create elements that respond to mouse position:

CSS
.spotlight {
  background: radial-gradient(
    circle at var(--mouse-x, 50%) var(--mouse-y, 50%),
    rgba(255,255,255,0.1),
    transparent 50%
  );
}
JavaScript
document.addEventListener('mousemove', (e) => {
  const x = (e.clientX / window.innerWidth) * 100;
  const y = (e.clientY / window.innerHeight) * 100;

  document.documentElement.style.setProperty('--mouse-x', x + '%');
  document.documentElement.style.setProperty('--mouse-y', y + '%');
});

Scroll-Based Styles

Change styles based on scroll position:

CSS
.header {
  background: rgba(255, 255, 255, var(--header-opacity, 0));
  box-shadow: 0 2px 4px rgba(0, 0, 0, calc(var(--header-opacity, 0) * 0.1));
}
JavaScript
window.addEventListener('scroll', () => {
  const scrolled = Math.min(window.scrollY / 200, 1);
  document.documentElement.style.setProperty('--header-opacity', scrolled);
});

Responsive Font Size Slider

Let users control the base font size:

CSS
:root {
  --base-font-size: 16px;
}

body {
  font-size: var(--base-font-size);
}
JavaScript
const fontSlider = document.querySelector('#font-size-slider');

fontSlider.addEventListener('input', (e) => {
  document.documentElement.style.setProperty(
    '--base-font-size',
    e.target.value + 'px'
  );
  localStorage.setItem('fontSize', e.target.value);
});

// Restore saved size
const savedSize = localStorage.getItem('fontSize');
if (savedSize) {
  document.documentElement.style.setProperty('--base-font-size', savedSize + 'px');
  fontSlider.value = savedSize;
}

Animation Control

Use CSS variables to control animation parameters dynamically:

CSS
.animated-box {
  animation: bounce var(--duration, 1s) var(--easing, ease) infinite;
  transform: scale(var(--scale, 1));
}

@keyframes bounce {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(var(--bounce-height, -20px)); }
}
JavaScript
// Speed control
document.querySelector('#speed').addEventListener('input', (e) => {
  document.documentElement.style.setProperty('--duration', e.target.value + 's');
});

// Bounce height control
document.querySelector('#height').addEventListener('input', (e) => {
  document.documentElement.style.setProperty('--bounce-height', -e.target.value + 'px');
});

Performance Considerations

  • Changing CSS variables on :root triggers a repaint of the entire page
  • For better performance, set variables on specific elements when possible
  • Use requestAnimationFrame for high-frequency updates (mouse tracking, scroll)
JavaScript
// Better performance for frequent updates
document.addEventListener('mousemove', (e) => {
  requestAnimationFrame(() => {
    document.documentElement.style.setProperty('--mouse-x', e.clientX + 'px');
    document.documentElement.style.setProperty('--mouse-y', e.clientY + 'px');
  });
});

Summary

CSS variables combined with JavaScript create powerful dynamic styling capabilities. Use getComputedStyle() to read variables and setProperty() to modify them at runtime. This enables dark mode toggles, user preference controls, mouse-tracking effects, scroll-based styling, and animation parameter control. Store user preferences in localStorage for persistence across sessions.