CSS Custom Properties (Variables)
Define once, use everywhere — and change them at runtime
Declaring and Using Variables
Declare custom properties with the -- prefix. Use them with the var() function. Typically you declare global variables on :root:
:root {
--primary: #6366f1;
--radius: 8px;
--spacing: 16px;
}
.button {
background: var(--primary);
border-radius: var(--radius);
padding: var(--spacing);
}
Fallback Values
The second argument to var() is a fallback used when the variable is not defined:
.card {
/* If --card-bg is not set, use #f8fafc */
background: var(--card-bg, #f8fafc);
/* Chained fallback */
color: var(--card-text, var(--text, #1e293b));
}
The fallback only triggers when the variable is completely undefined. If the variable is set to an invalid value for the property, the fallback is not used — the property falls back to its inherited or initial value instead.
Scoping
Variables cascade and inherit just like regular CSS properties. You can override them on any element:
:root {
--accent: #6366f1; /* default purple */
}
.section-danger {
--accent: #ef4444; /* override to red in this section */
}
.button {
background: var(--accent); /* purple by default, red inside .section-danger */
}
Default
Danger Scope
Success Scope
Dark Mode Theming
CSS variables make light/dark mode trivial. Define your color tokens once, swap them in a media query or class toggle:
:root {
--bg: #ffffff;
--text: #1e293b;
--surface: #f8fafc;
--border: #e2e8f0;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #0f172a;
--text: #e2e8f0;
--surface: #1e293b;
--border: #334155;
}
}
/* Or toggle with a class */
.dark {
--bg: #0f172a;
--text: #e2e8f0;
}
Every element using these variables updates automatically. No duplicate CSS needed.
Changing Variables with JavaScript
Unlike preprocessor variables, CSS custom properties are live. You can read and write them from JS:
// Set a variable on :root
document.documentElement.style.setProperty('--primary', '#ef4444');
// Set on a specific element
element.style.setProperty('--card-width', '300px');
// Read a variable's computed value
const value = getComputedStyle(document.documentElement)
.getPropertyValue('--primary');
This is powerful for user-customizable themes, sliders that control spacing or colors, and animations driven by scroll position.
Practical Patterns
Component API
/* The component exposes these variables for customization */
.card {
--card-padding: 16px;
--card-radius: 8px;
--card-bg: var(--surface);
padding: var(--card-padding);
border-radius: var(--card-radius);
background: var(--card-bg);
}
/* Consumers override without touching the component CSS */
.compact .card {
--card-padding: 8px;
--card-radius: 4px;
}
Responsive Variables
:root {
--container-padding: 16px;
--heading-size: 1.5rem;
}
@media (min-width: 768px) {
:root {
--container-padding: 32px;
--heading-size: 2rem;
}
}
@media (min-width: 1280px) {
:root {
--container-padding: 48px;
--heading-size: 2.5rem;
}
}
Frequently Asked Questions
What is the difference between CSS variables and Sass variables?
CSS variables are live in the browser — they cascade, inherit, and can be changed with JavaScript at runtime. Sass variables compile to static values at build time. Use CSS variables for theming and dynamic values. Use Sass variables for build-time constants.
Can CSS variables be used in media queries?
Not in the condition (@media (min-width: var(--bp)) does not work). But you can change variable values inside media queries: @media (min-width: 768px) { :root { --spacing: 32px; } }. All elements using that variable update automatically.
How do I set a fallback value for a CSS variable?
Use the second argument: var(--primary, #6366f1). If --primary is not defined, it uses #6366f1. You can chain: var(--a, var(--b, red)).