(Ab)Using CSS Variables from JavaScript Dynamically
CSS variables (also known as custom properties) are one of the most powerful additions to CSS in recent years. But when JavaScript enters the scene, they become even more dynamic. In this article, we’ll dive deep into how to manipulate CSS variables from JavaScript, and why this approach can be a performant, elegant solution for responsive themes, user-preference toggles, animations, and even real-time visualizations — all without relying on a frontend framework.
1. Understanding CSS Variables and Their Scope
Before we manipulate anything with JavaScript, it’s important to familiarize ourselves with how CSS variables work.
:root {
--primary-color: #3498db;
--font-size: 16px;
}
body {
background-color: var(--primary-color);
font-size: var(--font-size);
}
CSS variables declared under :root
are globally accessible, which is ideal for theming. Unlike preprocessor variables (like those in Sass), these are live and reactive — meaning they can be changed at runtime using JavaScript.
2. Reading and Writing CSS Variables in JavaScript
To interact with CSS variables from JavaScript, we use the getComputedStyle
and setProperty
APIs.
// Accessing a style value
const root = document.documentElement;
const styles = getComputedStyle(root);
const currentColor = styles.getPropertyValue('--primary-color');
console.log(currentColor); // e.g., #3498db
// Changing a CSS variable dynamically
root.style.setProperty('--primary-color', '#e74c3c');
This simple technique allows you to gear parts of your UI (like branding colors) toward interactivity — for example, changing themes based on user actions.
3. Dynamic Theming Made Simple
Let’s build a basic light/dark theme toggle using only CSS variables and vanilla JS, with zero frameworks or build tools.
/* theme.css */
:root {
--background-color: white;
--text-color: black;
}
[data-theme="dark"] {
--background-color: #111;
--text-color: #eee;
}
body {
background-color: var(--background-color);
color: var(--text-color);
}
// toggle-theme.js
const toggleTheme = () => {
const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', newTheme);
};
document.getElementById('theme-toggle').addEventListener('click', toggleTheme);
When a user clicks the toggle, we simply switch the data-theme
attribute. Since our CSS uses this attribute to scope alternate variables, we get a full theme switch with a single DOM operation.
4. Animating with JavaScript + CSS Variables
CSS variables can even power animations. Consider an interactive button that lights up based on a mouse position.
/* In CSS */
.button {
position: relative;
background: radial-gradient(circle at var(--x, 50%) var(--y, 50%), #ff0, #f00);
transition: background 0.1s;
}
// In JS
const button = document.querySelector('.button');
button.addEventListener('mousemove', e => {
const rect = button.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
button.style.setProperty('--x', `${x}px`);
button.style.setProperty('--y', `${y}px`);
});
This real-time responsiveness is hard to replicate with pure JS or CSS alone. By combining both, you get smooth, GPU-accelerated feedback loops. And since we’re modifying variables, any number of CSS rules can respond to the changes without touching classes or styles directly.
5. Performance and Best Practices
While using JavaScript to update CSS variables is powerful, it comes with caveats you’ll want to consider:
- Throttle rapid updates (e.g., mousemove) using
requestAnimationFrame()
to avoid layout thrashing. - Scope wisely: Apply CSS variables at specific elements rather than globally if only certain DOM parts need them.
- Use fallbacks in
var()
syntax to prevent unexpected values:var(--foo, #000)
.
// Using requestAnimationFrame to optimize reflows
let rafId;
button.addEventListener('mousemove', (e) => {
if (rafId) return;
rafId = requestAnimationFrame(() => {
const rect = button.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
button.style.setProperty('--x', `${x}px`);
button.style.setProperty('--y', `${y}px`);
rafId = null;
});
});
CSS variables won’t trigger a full repaint as inline styles might. They are a highly optimized part of modern rendering pipelines, especially when used for colors, spacing, and transforms.
Conclusion
Whether you’re designing a theme manager, building interactive components, or tweaking animations based on real-time input, leveraging CSS variables via JavaScript lets you write less DOM-manipulating code with consistent styling logic. It’s not hacking — it’s unlocking native interop between style and behavior.
No frameworks. No build tools. Native capabilities, fully harnessed.
Useful links: