What are CSS custom properties (variables) and how do they differ from preprocessor variables?
Learn how CSS custom properties differ from Sass/Less preprocessor variables: runtime vs compile-time, cascading, theming, and JS access — with examples.
Expected Interview Answer
CSS custom properties (also called CSS variables) are values declared with a -- prefix and read with the var() function; they are live, native to the browser, and resolved at runtime. Preprocessor variables (like Sass $vars or Less @vars) are resolved at compile time and no longer exist in the final CSS.
The core difference is when and where the value lives. Custom properties are part of the cascade and the DOM: they inherit, can be scoped to any selector, respond to media queries and pseudo-classes, and can be read and changed at runtime with JavaScript. Preprocessor variables are compiled away into static values before the browser ever sees them, so they cannot change based on the DOM state or be updated live. This makes custom properties ideal for theming, runtime toggles, and component APIs, while preprocessor variables suit build-time constants and logic like loops and mixins.
- Custom properties resolve at runtime and live in the cascade
- They inherit and can be scoped to any element or state
- JavaScript can read and update them dynamically
- Ideal for theming (light/dark) and design tokens
- Respond to media queries, :hover, and container changes
- No build step required — native browser feature
AI Mentor Explanation
A preprocessor variable is a team sheet printed before the match — once printed, the names are fixed for the whole game. A CSS custom property is the live scoreboard: it can be read and rewritten during play, changes as the innings progresses, and different sections of the ground can show their own scoped version updated in real time.
Step-by-Step Explanation
Step 1
Declare the property
Define a custom property on a selector, commonly :root for global scope, using the -- prefix: --brand: #2563eb.
Step 2
Consume with var()
Reference it anywhere with var(--brand), optionally providing a fallback: var(--brand, #000).
Step 3
Scope and override
Re-declare the property on a child selector or state to override it for that subtree — it inherits down the cascade.
Step 4
React to context
Change values inside media queries, :hover, or a [data-theme] attribute to enable live theming.
Step 5
Update at runtime
Read and set values from JavaScript with getPropertyValue and style.setProperty for dynamic UIs.
What Interviewer Expects
- Runtime (browser) vs compile-time (build) resolution
- Custom properties participate in the cascade and inherit
- Ability to read/update via JavaScript at runtime
- Scoping to selectors and states, not just global
- Correct use cases: theming vs build-time logic/loops
Common Mistakes
- Claiming Sass/Less variables can change at runtime
- Thinking custom properties need a build step or compiler
- Forgetting the var() fallback argument for resilience
- Assuming custom properties don't inherit or cascade
- Trying to use custom properties inside Sass logic (loops, math) where they don't evaluate at compile time
Best Answer (HR Friendly)
“CSS custom properties are variables the browser understands natively — you can change them live, even with JavaScript, and they cascade down like other styles, which makes them great for things like light and dark themes. Preprocessor variables like Sass are calculated when the code is built and then disappear, so they can't change while the page is running.”
Code Example
:root {
--brand: #2563eb;
--text: #111827;
--space: 16px;
}
[data-theme="dark"] {
--brand: #60a5fa;
--text: #f9fafb;
}
.button {
/* var() reads the current cascaded value, with a fallback */
background: var(--brand, #000);
color: var(--text);
padding: var(--space);
}
.card:hover {
/* override the variable just for this subtree/state */
--brand: #1e40af;
}/* This is JavaScript, not available with compiled Sass/Less variables */
const root = document.documentElement;
const brand = getComputedStyle(root).getPropertyValue('--brand');
root.style.setProperty('--brand', '#dc2626');Follow-up Questions
- How does var() fallback work and when is it useful?
- Can custom properties be used inside a Sass loop or math expression?
- How would you implement a dark-mode toggle with custom properties?
- Do custom properties inherit, and how do you scope one to a component?
- What is the performance impact of many runtime custom properties?
MCQ Practice
1. When are CSS custom properties resolved?
Custom properties are a native browser feature resolved at runtime; they live in the cascade and can change based on DOM state.
2. Which is true of Sass ($) variables but NOT of CSS custom properties?
Sass variables are resolved at compile time and do not exist in the shipped CSS, so they cannot change or be read at runtime.
3. What does var(--gap, 8px) do?
The second argument to var() is a fallback used when the custom property is not defined or is invalid.
Flash Cards
CSS custom property — A --prefixed value read via var(); native, runtime-resolved, cascades and inherits, editable by JavaScript.
Preprocessor variable — A Sass $ or Less @ value resolved at compile time; gone from the shipped CSS and unchangeable at runtime.
Key difference — Custom properties are live and part of the DOM/cascade; preprocessor variables are static build-time constants.
var() fallback — var(--x, fallbackValue) uses the fallback when --x is undefined or invalid.
Best use of custom properties — Theming (light/dark), design tokens, runtime toggles, and component-scoped style APIs.