CSS Custom Properties (Variables): A Practical 2026 Guide

CSS custom properties (variables) are one of the biggest CSS additions of the last decade — and until I really understood them, I was writing 800-line Sass files just to change one brand color. Unlike Sass variables (which are compiled away), custom properties live in the browser, can be changed at runtime from JavaScript, and inherit through the DOM. That combination is what makes them perfect for theming, dark mode, and component APIs in pure CSS.
The absolute basics
:root {
--primary: oklch(0.65 0.18 250);
--radius: 12px;
--space-3: 1rem;
}
.button {
background: var(--primary);
border-radius: var(--radius);
padding: var(--space-3);
}The double-dash prefix is mandatory. Read them back with var(--name) and always provide a fallback in nested components: var(--primary, #06f).
Dark mode in 10 lines
:root {
--bg: white;
--fg: #111;
}
[data-theme="dark"] {
--bg: #0b0b0e;
--fg: #f5f5f7;
}
body { background: var(--bg); color: var(--fg); }That's it. Every rule that references --bg / --fg re-computes automatically when you flip the data-theme attribute. No re-compile, no page reload, no class swapping across a thousand elements.
Respect the user's OS preference
@media (prefers-color-scheme: dark) {
:root { --bg: #0b0b0e; --fg: #f5f5f7; }
}Fluid typography with clamp()
:root {
--step--1: clamp(0.875rem, 0.8rem + 0.3vw, 1rem);
--step-0: clamp(1rem, 0.95rem + 0.4vw, 1.125rem);
--step-1: clamp(1.25rem, 1.1rem + 0.75vw, 1.5rem);
--step-3: clamp(2rem, 1.6rem + 2vw, 3rem);
}
h1 { font-size: var(--step-3); }
p { font-size: var(--step-0); }One clamp() per step, one variable per element. Zero media queries. This scales the entire type ramp smoothly from 320px phones to 27-inch monitors.
Scoping variables to components — the prop pattern
.card {
--card-padding: 1rem;
--card-bg: white;
padding: var(--card-padding);
background: var(--card-bg);
}
/* override per instance, without a modifier class */
.card.--dense { --card-padding: 0.5rem; }
.card.--dark { --card-bg: #111; color: white; }This is basically React props for CSS: the component defines its own API and every consumer sets values without touching internals.
Talking to JavaScript
// Read
const primary = getComputedStyle(document.documentElement)
.getPropertyValue('--primary');
// Write (drives things like real-time theme editors)
document.documentElement.style.setProperty('--primary', '#ff5500');Animating variables with @property
@property --angle {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
.card:hover { --angle: 180deg; transition: --angle 0.4s ease; }
.card { background: conic-gradient(from var(--angle), #06f, #f06); }Common gotchas I've hit
- Custom properties inherit — great for theming, dangerous inside :hover. Reset on state changes.
- var() fallbacks are silent — a typo like var(--pirmary) just becomes 'invalid' and the property is dropped.
- You can't use custom properties in media query values (@media (min-width: var(--bp)) doesn't work).
- Sass variables and CSS variables are not the same — Sass is build-time, CSS is runtime.
Frequently asked questions
Can I animate CSS variables?
Yes, with the @property at-rule that lets you register types like <color>, <length>, or <angle>. Without @property the browser can't interpolate and animations snap.
Sass variables or CSS custom properties?
Use CSS custom properties for anything that could change at runtime (theme, mode, component prop). Keep Sass variables for build-time-only things you never expose to the browser — though honestly in 2026 I default to CSS custom properties for almost everything.
Are CSS variables slow?
No. Modern browsers resolve them extremely fast. The one place to be careful is thousands of elements each defining their own variables — prefer defining once on :root or a component root and inheriting down.
Can I use custom properties for media query breakpoints?
Not directly — media queries evaluate before variable substitution. Use Sass mixins or PostCSS if you need to share breakpoint values between JS and CSS.
External references
Enjoyed this article?
Share it with a fellow developer or explore more tutorials in our blog.
More articles