CSS Custom Properties
CSS variables that cascade and inherit — define tokens, theme at runtime, fall back safely, and avoid the invalid-at-computed-value trap.
- css
- custom-properties
- variables
Custom properties (--name) are CSS values you define and reuse with var(). Unlike preprocessor variables, they are live in the browser: they inherit, cascade, and can change at runtime with a class, media query, or one line of JavaScript.
Docs: MDN using CSS custom properties, var().
Define and consume
:root {
--brand: #0b6bcb;
--radius: 0.5rem;
--space-2: 0.5rem;
--font-sans: system-ui, sans-serif;
}
.button {
background: var(--brand);
border-radius: var(--radius);
padding: var(--space-2) calc(var(--space-2) * 2);
font-family: var(--font-sans);
}
Names are case-sensitive. By convention tokens live on :root or a theme scope; component-local vars live on the component root.
Fallbacks
color: var(--text, #14212b);
background: var(--card-bg, var(--surface, white));
The fallback is used when the property is missing or invalid at computed-value time on that element — not as a browser-support polyfill for var() itself.
Inheritance and scope
.card {
--pad: 1rem;
padding: var(--pad);
}
.card--compact {
--pad: 0.5rem; /* children using var(--pad) update too */
}
<article class="card card--compact">
<p style="margin: 0; padding: var(--pad)">Inherits --pad</p>
</article>
This is the theming superpower: set --brand on .theme-dark or [data-theme="dark"] and the tree retints.
Runtime theming
document.documentElement.style.setProperty("--brand", "#c45c26");
[data-theme="dark"] {
--surface: #0f1419;
--text: #e7ecf1;
--brand: #6cb6ff;
}
No rebuild required for simple theme switches. Prefer class/data-* on html over sprinkling inline styles.
Invalid at computed-value time
:root {
--gap: 8;
}
.row {
/* Wrong: unitless 8 is invalid for gap → property becomes invalid */
gap: var(--gap);
}
.row-fixed {
gap: calc(var(--gap) * 1px); /* OK if --gap is number */
}
If var() resolves to something illegal for that property, the declaration is invalid and the property falls back to inherited/initial — not necessarily to a previous line in the same rule. Store full values (8px) or always calc with units.
What custom props are bad at
- Media query break points as
var(--bp)in@media (min-width: var(--bp))— generally not allowed (MQ syntax needs certain tokens). - Replacing entire selector logic — vars hold values, not selectors.
- Huge binary data in vars — keep tokens small.
Interview out-loud
“Custom properties are runtime CSS variables defined with --name and read with var(). They inherit and cascade, so theming is overriding tokens on a parent. I put design tokens on :root, component tokens on the component root, always include units or calc carefully, and use fallbacks as safety defaults. They differ from Sass variables because the browser computes them per element.”
Footguns
- Unitless numbers in length contexts.
- Expecting
varin every at-rule syntax position. - Circular references (
--a: var(--b); --b: var(--a)). - Setting hundreds of vars per frame in JS — style recalc cost.
- Using props for one-off magic numbers with no naming discipline.
Component API with vars
Expose a small public token surface on the host element; keep private vars underscored.
.tabs {
--tabs-gap: 0.5rem;
--tabs-indicator: var(--brand);
display: flex;
gap: var(--tabs-gap);
}
.tabs__tab[aria-selected="true"] {
box-shadow: inset 0 -2px 0 var(--tabs-indicator);
}
/* Consumer override */
.tabs.tabs--compact {
--tabs-gap: 0.25rem;
}
This is how design systems let product teams theme without forking component CSS. Register required tokens in Storybook/docs. Avoid 40 public vars on one component — three to eight is usually enough.
Debugging
In DevTools, select an element → Computed → show custom properties (or the Styles panel lists inherited vars). If a var() is invalid, the property is crossed out and you will not see a “fallback cascade” like Sass; fix the resolved value or supply var(--x, fallback).
Related
- Theming with custom properties
- color-mix and relative color
- Cascade & specificity
- prefers-color-scheme
Further reading
Related guides
- Theming with Custom PropertiesBuild light/dark and brand themes with CSS variables — token layers, data-theme switches, component contracts, and FOUC avoidance.
- Accent Color and Form ControlsUse accent-color to theme native checkboxes, radios, ranges, and progress — what it paints, what it skips, and when to custom-style instead.
- Auto-fit vs Auto-fillauto-fit collapses empty tracks; auto-fill keeps them. Use both with minmax for responsive card grids without media-query soup.
- BEM Naming MethodologyBlock, Element, Modifier naming for CSS — how it limits specificity wars, what to name, and when utility or CSS modules beat strict BEM.
- blend-mode Basicsmix-blend-mode vs background-blend-mode — how pixels combine, isolation, and the stacking/readability traps in product UI.