CSS: Custom Properties (CSS Variables) vs. Sass/Less Variables
A core CSS architecture and layout design interview question is:
What are CSS Custom Properties (CSS Variables)? How do they differ from preprocessor variables (like Sass or Less), and what benefits do they bring to dynamic components and dark mode theme structures?
Historically, developers used preprocessors like Sass or Less to introduce variable tokens into stylesheets. While preprocessors remain popular, native CSS Custom Properties offer powerful runtime capabilities that compilation tools cannot replicate.
1. Native Custom Properties vs. Sass Variables
The fundamental difference lies in when the variables are resolved:
- Preprocessor Variables (Sass/Less): Evaluated at compile time. Once compiled, the variables are replaced by static CSS values. The browser never sees the variable definitions.
- CSS Custom Properties (Native): Evaluated at runtime by the browser. They remain part of the active stylesheet structure and can change dynamically based on DOM state or Javascript variables.
2. Technical Comparison
A. Scoping and Cascading
Native variables follow the cascade. They inherit values from parent containers, meaning you can redefine them locally inside child nodes. Preprocessor variables have lexical block scoping and cannot inherit down the DOM:
/* Native CSS */
:root {
--theme-color: #333;
}
.dark-card {
--theme-color: #fff; /* Redefined locally for this element and its children */
}
.card-title {
color: var(--theme-color); /* Automatically matches scope */
}In Sass, variable declarations do not interact with the DOM structure, so you cannot override a variable value dynamically based on a class name like .dark-card on the fly.
B. JavaScript Integration
Since CSS Custom Properties exist in the browser, they can be read or modified dynamically using JavaScript:
// Modify the variable on the root document level
document.documentElement.style.setProperty('--theme-color', '#ff0000');
// Read the variable value from an element
const color = getComputedStyle(element).getPropertyValue('--theme-color');Sass variables are completely destroyed during compilation, so JS cannot inspect or modify them.
3. Implementation: Dark Mode/Theming
CSS Custom Properties make theme switching incredibly simple and performant. Instead of duplicating selectors for light and dark classes, you only toggle variable values:
:root {
--bg-color: #ffffff;
--text-color: #1a1a1a;
--accent-color: #0066cc;
}
/* Redefine variables under the dark data attribute */
[data-theme="dark"] {
--bg-color: #121212;
--text-color: #f5f5f5;
--accent-color: #3399ff;
}
/* Components write clean, single rules */
body {
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 0.3s, color 0.3s;
}
button {
background-color: var(--accent-color);
}To toggle themes, JavaScript only needs to set a single attribute on the <html> or <body> tag:
document.documentElement.setAttribute('data-theme', 'dark');Senior-Level Interview Answer
CSS Custom Properties represent native runtime variables that inherit and cascade through the DOM. Preprocessor variables (such as Sass or Less) compile down to static CSS values during build stages, whereas custom properties persist in the browser. This difference allows native variables to be manipulated via JavaScript, follow media queries natively, and cascade through nested styles. For applications featuring dynamic theme configurations or component-level variations, custom properties avoid code duplication by letting you swap variable values rather than generating complete, duplicate selector trees.
Common Interview Mistakes
❌ Expecting Sass mixins to compile native CSS variables
Sass variables are resolved during the compilation step. You cannot pass a dynamic native CSS variable into a Sass math function or built-in color manipulation function (like lighten(var(--primary-color), 10%)) because Sass evaluates code before the browser resolves the CSS variable value.
❌ Forgetting fallbacks
If a CSS custom property is undefined, the browser defaults to inheriting or ignores it. You should always specify a fallback value inside the var() declaration for critical styles, like: var(--theme-color, #000).
Key Takeaways
- Runtime Execution: Custom properties are interpreted by the browser, whereas Sass variables are compiled away at build time.
- Cascade Inheritance: Native variables follow DOM hierarchy rules and can be redefined locally inside target elements.
- JS Interoperability: JavaScript can read or mutate custom property values dynamically using
style.setProperty. - Media Query Aware: Native variables adapt to media query changes (like
@media (prefers-color-scheme: dark)) natively inside CSS files. - Fallback Protection: Support fallback values inside the
var()execution (var(--name, fallback)) to prevent styling failures.