Detailed explanation
CSS custom properties are author-defined properties that hold reusable CSS values. Their names start with two hyphens, such as --primary-color or --spacing, and you read them using var(). For example, :root { --primary-color: #2563eb; --spacing: 16px; } defines shared values, while .button { background: var(--primary-color); padding: var(--spacing); } uses them. They help keep colours, spacing, and other design decisions consistent without repeating values across many selectors. Custom property names are case-sensitive, so --spacing and --Spacing are different properties.
Unlike simple text replacements, custom properties participate in the CSS cascade and inherit by default. The :root selector targets the document’s root element, usually <html>, making values available to descendants through inheritance rather than creating a separate global variable system. A component can declare .card { --spacing: 12px; } to change the value for itself and its descendants without affecting unrelated elements. Normal cascade rules determine which declaration wins on an element. For responsive spacing, @media (max-width: 600px) { :root { --spacing: 8px; } } changes the inherited value when the viewport matches.
The browser resolves var() while computing styles, using the custom property value available on the element. You can provide a fallback: padding: var(--spacing, 16px). This handles a missing or otherwise invalid custom property, but does not validate whether a defined value fits the destination property. If --spacing is red, padding: var(--spacing, 16px) becomes invalid at computed-value time; the fallback is not selected. Custom properties generally store token sequences rather than strongly typed values. Use calc() for calculations, such as width: calc(var(--columns) * 20px), instead of trying to concatenate a number and a unit.
Because custom properties remain in the browser, JavaScript can update them using document.documentElement.style.setProperty('--primary-color', '#dc2626'). Dependent styles are then recalculated. This supports themes and interactive controls without editing every affected rule. Sass variables, by contrast, are resolved during compilation and do not themselves participate in runtime inheritance. Interviewers typically expect correct syntax, an understanding of scope and the cascade, and a practical example showing component overrides, responsive values, or theme switching.
Key points to remember
- Declare custom properties with --name and reference them with var(--name).
- Use :root for document-wide defaults inherited by descendants.
- Override values on components or inside media queries using normal cascade rules.
- Use var(--name, fallback), but remember it does not fix a defined value with an unsuitable type.
- Update values at runtime with JavaScript; Sass variables are resolved at compile time.
Common mistakes candidates make
- Assuming custom properties are globally scoped regardless of where they are declared.
- Expecting var(--spacing, 16px) to use 16px when --spacing is defined as red.
- Writing var(--size)px to attach a unit instead of storing the unit or using calc(var(--size) * 1px).
Likely follow-up questions
How would you implement light and dark themes?
Define default colour properties on :root and override them in a selector such as :root[data-theme='dark']. Components reference those properties, so changing the root attribute updates their colours without rewriting component rules.
Can you use a custom property in a media query condition?
Standard CSS does not allow @media (max-width: var(--breakpoint)). You can, however, declare or override custom properties inside a media query with a supported condition.
What does @property add to custom properties?
The @property rule can register a custom property with a syntax constraint, an inheritance setting, and an initial value. Registering an appropriate type can also enable smooth interpolation in animations rather than discrete value changes.
Interview tip: Start with the declaration and var() syntax, demonstrate a component override, and finish by contrasting runtime custom properties with Sass variables.
