CSS Variables & Custom Properties
What are CSS variables?
CSS variables (officially called custom properties) let you store values and reuse them throughout your stylesheet. Instead of repeating the same color or spacing value in 50 places, you define it once and reference it everywhere.
CSS
:root {
--color-primary: #2563eb;
--color-text: #1e293b;
--spacing-md: 1rem;
--radius: 8px;
}
.button {
background: var(--color-primary);
color: white;
padding: var(--spacing-md);
border-radius: var(--radius);
}
.link {
color: var(--color-primary);
}Change
--color-primary in one place, and every button, link, and accent color updates.Defining variables
Variables are defined with
-- prefix and used with var():CSS
:root {
--font-sans: system-ui, -apple-system, sans-serif;
--max-width: 1200px;
}
body {
font-family: var(--font-sans);
max-width: var(--max-width);
}:root targets the <html> element and makes variables available globally. You can also define them on any element to scope them.Fallback values
If a variable isn't defined,
var() accepts a fallback:CSS
.card {
color: var(--card-text, #333);
padding: var(--card-padding, 1rem);
}Scoped variables
Variables cascade like any CSS property. Define them on a specific element to override the global value:
CSS
:root {
--bg: #ffffff;
--text: #1e293b;
}
.dark-section {
--bg: #0f172a;
--text: #f1f5f9;
}
.card {
background: var(--bg);
color: var(--text);
}A
.card inside .dark-section automatically uses the dark values. This is how theme systems work.Building a dark mode
CSS
:root {
--bg: #ffffff;
--bg-card: #f8fafc;
--text: #1e293b;
--text-muted: #64748b;
--border: #e2e8f0;
--accent: #2563eb;
}
[data-theme="dark"] {
--bg: #0f172a;
--bg-card: #1e293b;
--text: #f1f5f9;
--text-muted: #94a3b8;
--border: #334155;
--accent: #3b82f6;
}
body {
background: var(--bg);
color: var(--text);
}
.card {
background: var(--bg-card);
border: 1px solid var(--border);
}Toggle dark mode by adding/removing
data-theme="dark" on <html>:JavaScript
document.documentElement.setAttribute('data-theme', 'dark');Every element that uses CSS variables updates instantly. No page reload needed.
Variables with calculations
Combine variables with
calc():CSS
:root {
--space-unit: 0.25rem;
}
.card {
padding: calc(var(--space-unit) * 4); /* 1rem */
margin-bottom: calc(var(--space-unit) * 6); /* 1.5rem */
}Dynamic values with JavaScript
CSS variables can be read and written from JavaScript:
JavaScript
document.documentElement.style.setProperty('--accent', '#dc2626');
const accent = getComputedStyle(document.documentElement).getPropertyValue('--accent');This is powerful for user preferences, dynamic theming, or adjusting layouts based on runtime data.
Variables vs preprocessor variables (Sass/Less)
| Feature | CSS Variables | Sass Variables |
|---|---|---|
| Runtime | Yes — change anytime | No — compiled once |
| Cascade | Yes — can override per element | No — flat scope |
| JS access | Yes | No |
| Browser support | All modern browsers | Compiled to plain CSS |
CSS variables are strictly more powerful for anything that changes at runtime (themes, user preferences, responsive adjustments).
Key takeaway
CSS variables eliminate repetition and enable dynamic theming. Define them on
:root for globals, scope them on elements for overrides, and use them with calc() for computed values. They're the foundation of any maintainable design system.