What Are CSS Custom Properties?
CSS Custom Properties (often called CSS variables) let you store values in reusable named containers that can be changed dynamically — even at runtime with JavaScript. Unlike preprocessor variables (SASS/LESS), they live in the browser and respond to the cascade.
:root {
--color-primary: #6366f1;
--spacing-md: 1rem;
--radius-lg: 0.75rem;
}
.button {
background: var(--color-primary);
padding: var(--spacing-md);
border-radius: var(--radius-lg);
}
Syntax Deep Dive
/* Define */
--property-name: value;
/* Use */
color: var(--color-text);
/* With fallback */
color: var(--color-text, #333);
/* Fallback can reference another variable */
color: var(--color-heading, var(--color-text, black));
Properties defined on :root are globally available. Properties defined on an element are scoped to it and its children — this is their power.
Building a Theme System
:root {
/* Colors */
--color-bg: #ffffff;
--color-surface: #f8fafc;
--color-text: #0f172a;
--color-text-muted: #64748b;
--color-primary: #6366f1;
--color-primary-hover: #4f46e5;
--color-border: #e2e8f0;
/* Typography */
--font-sans: 'Inter', system-ui, sans-serif;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
/* Spacing */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-4: 1rem;
--space-8: 2rem;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
}
Dark Mode in 10 Lines
:root {
--color-bg: #ffffff;
--color-text: #0f172a;
--color-surface: #f8fafc;
--color-border: #e2e8f0;
}
[data-theme="dark"] {
--color-bg: #0f172a;
--color-text: #f1f5f9;
--color-surface: #1e293b;
--color-border: #334155;
}
function toggleDarkMode() {
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
document.documentElement.setAttribute('data-theme', isDark ? 'light' : 'dark');
localStorage.setItem('theme', isDark ? 'light' : 'dark');
}
Respecting System Preference
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #0f172a;
--color-text: #f1f5f9;
}
}
Combine with the data-theme attribute for a manual override that respects system default.
Component-Scoped Variables
.card {
--card-padding: 1.5rem;
--card-radius: 0.75rem;
--card-shadow: var(--shadow-md);
padding: var(--card-padding);
border-radius: var(--card-radius);
box-shadow: var(--card-shadow);
}
.card--compact {
--card-padding: 0.75rem; /* override just for compact variant */
}
Updating Variables at Runtime with JavaScript
// Set a variable
document.documentElement.style.setProperty('--color-primary', '#e11d48');
// Read a variable
const primary = getComputedStyle(document.documentElement)
.getPropertyValue('--color-primary').trim();
This enables user-customizable themes, A/B testing colors, and dynamic UI changes without class toggling.
Animating with CSS Variables
.progress-bar {
--progress: 0;
width: calc(var(--progress) * 1%);
transition: width 0.3s ease;
}
bar.style.setProperty('--progress', '75'); // animate to 75%
Conclusion
CSS custom properties are one of the most powerful features in modern CSS. Once you start building with a design token system, dark mode becomes trivial, component variants become clean, and runtime theming becomes straightforward. Replace your hardcoded values today.