Lesson 3 of 30
Custom properties are CSS variables is the beginner explanation. The interview-relevant part is how they inherit, cascade, and fail silently when they're wrong.
12 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
Question 1 of 3
`--spacing: 12px;` is set correctly, but a typo elsewhere references `var(--spacng)` (misspelled) with no fallback provided. What happens?
Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
A custom property (written --name: value) is a value you define once and reference elsewhere with var(--name). That much is the beginner explanation. What actually matters at a working-developer level is that custom properties are regular CSS properties — which means they follow the same rules every other property follows:
$variable), which is resolved once at build time and has no runtime concept of "which element is asking."color declaration wins also decide which --accent-color declaration wins — so you can override a custom property per-component, per-state, or inside a media query, exactly like any other property.var(--name, fallback) provides a fallback for when the property is unset or invalid, so a component can define a sensible default while still being overridable..button {
--button-bg: var(--color-accent, #3b82f6);
background: var(--button-bg);
padding: 0.5rem 1rem;
}
.button--danger {
--button-bg: var(--color-danger, #dc2626);
}
@media (prefers-color-scheme: dark) {
:root {
--color-accent: #60a5fa;
}
}
.button never hardcodes a color — it reads --button-bg, which resolves differently depending on where and when it's asked. .button--danger overrides it per-variant with a single line, no duplicated background declaration. The dark-mode media query overrides --color-accent at the root, and every button that inherits it updates automatically — one change, cascades everywhere it's used, exactly the way a token system is supposed to work.
color or font-family — a value set on :root or any ancestor flows down to every descendant unless something in between resets it. This is the mechanism that makes theme switching (dark mode, per-section accent colors) work with a single override point instead of touching every component.var(--acent-color) (misspelled) with no fallback simply resolves to nothing, which is much harder to spot in review than a build error would be. Always consider giving var() an explicit fallback for anything that isn't guaranteed to be set.--button-bg inside .button--danger doesn't require touching .button's own rule at all.@property exists as a newer, stricter alternative that lets you declare a custom property's syntax (type), inheritance behavior, and initial value explicitly — so an invalid value actually falls back to a declared default instead of silently doing nothing, and the property can even be animated smoothly (something plain custom properties can't do, since the browser doesn't know they're a color or a length without a declared type). It's the right reach when a design-token system needs real guarantees; treat it as the next step past this lesson, not something to reach for by default.var() chains, respond to media queries, and differ per element — a preprocessor variable is just text substitution before the browser ever sees the file, custom properties are a genuinely live, runtime part of the cascade."Why would you reach for a CSS custom property instead of a Sass variable for a theme value?" is a real question, and the answer that actually shows understanding is the runtime-versus-build-time distinction: custom properties can change per-element, per-state, and per-media-query without a rebuild, because they're inherited and cascaded like any other live CSS property.