The single biggest practical difference between a Sass variable and a CSS custom property is when it resolves — Sass variables are substituted at build time and are gone by the time CSS reaches the browser; custom properties are live values the browser evaluates at runtime, and that difference is what makes runtime theming and dynamic styling actually possible natively.
CSS custom properties (informally called CSS variables) let you define reusable values directly in CSS, referenced with var(), and — unlike preprocessor variables — resolved by the browser at runtime, meaning they participate in the cascade, can be changed dynamically via JavaScript, and automatically respond to media queries or class changes without a rebuild.
Why CSS Custom Properties Matter (and When They're Overkill)
For anything requiring runtime-changeable values — dark mode toggling, user-customizable themes, responsive values that change based on media queries without duplicating whole rule blocks — custom properties are the native mechanism, and they work without any build step or preprocessor, directly in plain CSS understood by every modern browser.
Custom properties are overkill for values that are genuinely fixed at build time and never need to change at runtime — a one-off spacing value used in a single place doesn't need the indirection of a custom property; reserve them for values that are actually shared, themed, or need runtime responsiveness.
Getting Started with CSS Custom Properties
Defining and using custom properties, scoped to :root for global availability:
:root {
--primary-color: #3b82f6;
--spacing-unit: 8px;
}
.button {
background-color: var(--primary-color);
padding: var(--spacing-unit) calc(var(--spacing-unit) * 2);
}
Runtime theme switching — just changing the custom property values, no rebuild needed:
[data-theme="dark"] {
--primary-color: #60a5fa;
--bg-color: #1a1a1a;
}
document.documentElement.setAttribute("data-theme", "dark");
Core CSS Custom Properties Concepts Every Developer Should Know
Custom properties cascade and inherit like any other CSS property. A custom property defined on a parent element is inherited by descendants unless overridden — this means you can scope theme overrides to specific sections of a page (a <div data-theme="dark"> wrapper) rather than only being able to theme the whole document globally.
var() accepts a fallback value as its second argument, used if the custom property isn't defined: var(--spacing, 8px) — useful for components that should work with a sensible default even when embedded somewhere that hasn't defined the expected custom property.
JavaScript can read and write custom properties directly via the CSSOM, enabling dynamic styling driven by application state without needing a CSS-in-JS library's runtime style injection:
document.documentElement.style.setProperty("--primary-color", userChosenColor);
Custom properties are not the same as Sass variables and solve a different problem. Sass variables are compile-time text substitution, gone once compiled — they can't be changed at runtime and don't participate in the cascade. Custom properties are live, cascading, runtime values. Using both together (Sass for compile-time logic, custom properties for runtime-changeable values) is a common and sensible combination, not a redundancy.
Common CSS Custom Properties Mistakes and How to Fix Them
Mistake 1: confusing custom properties with Sass/preprocessor variables, expecting compile-time-only behavior (like using them inside a media query definition itself) where a Sass variable would work but a custom property's runtime nature doesn't apply the same way. Fix: understand the distinct nature of each and use the one that actually fits the specific need (compile-time logic vs. runtime dynamism).
Mistake 2: defining custom properties without fallback values in components meant to be portable/reusable, causing silent breakage if used somewhere the expected custom property isn't defined. Fix: provide sensible fallback values via var()'s second argument for component-level custom properties.
Mistake 3: not taking advantage of cascading/scoping, defining every custom property globally on :root even when a value should really only apply to a specific component or section. Fix: scope custom properties to the appropriate element when the value isn't genuinely global, using the cascade deliberately.
When Should You Use CSS Custom Properties Instead of Sass Variables?
Use CSS custom properties for any value that needs to change at runtime — theming, dark mode, JavaScript-driven dynamic styling, or values that should respond to media queries without duplicating rule blocks. Use Sass variables (if already using Sass) for compile-time-only constants and logic — values that never need to change after build, or that feed into Sass's own compile-time functions and control flow.
CSS Custom Properties in Production
Use custom properties as the mechanism for theming and dark mode specifically, since their runtime, cascading nature is exactly suited to that use case without needing JavaScript-driven style recalculation. Also provide fallback values for custom properties used in reusable/shared components, since a missing custom property should degrade gracefully, not silently break the component's styling.
If your project currently implements theming by swapping entire stylesheets or duplicating rule blocks per theme, migrating to custom-property-based theming is a meaningfully simpler and more maintainable approach worth the refactor.