Variables make design tokens, theming and dark mode a matter of changing a few values.
A custom property is any property whose name starts with --. You define it like any declaration and read it with var():
:root { --brand: #4f46e5; }
.button { background: var(--brand); }They're not preprocessor variables: they live in the browser, take part in the cascade and inherit. Defining --brand on :root makes it available everywhere; redefining it on .danger-zone changes it for that subtree only. Change one with JavaScript (el.style.setProperty("--x", "10px")) and everything using it updates live.
var(--gap, 1rem) supplies a fallback used when the variable isn't defined. Fallbacks can nest: var(--a, var(--b, 4px)).
Values are stored as token streams and only checked when used. If --size: red is used in width: var(--size), the declaration becomes invalid at computed-value time and the property falls back to its inherited or initial value, not to an earlier declaration in your stylesheet.
Variables work well as design tokens: colours, spacing scale, radii, fonts. Theming becomes overriding tokens:
@media (prefers-color-scheme: dark) { :root { --bg: #111; --fg: #eee; } },- or a
[data-theme="dark"]attribute toggled by a script.
Variables can't be used in selectors or media query conditions. To animate one, or make it type-checked, register it with @property, covered with animations.
Example
<!-- index.html -->
<style>
:root {
--bg: #ffffff; --fg: #111827; --brand: #4f46e5;
--space: 1rem; --radius: 8px;
}
@media (prefers-color-scheme: dark) {
:root { --bg: #0b1020; --fg: #e5e7eb; }
}
body { background: var(--bg); color: var(--fg); }
.button {
background: var(--btn-bg, var(--brand)); /* local override, then token */
padding: calc(var(--space) / 2) var(--space);
border-radius: var(--radius);
color: white;
}
.danger { --btn-bg: crimson; } /* only this subtree changes */
</style>
<button class="button">Save</button>
<div class="danger"><button class="button">Delete</button></div>Remember
- --name defines, var(--name, fallback) reads
- Custom properties cascade and inherit, so they can be scoped
- Theming = redefining tokens under a media query or attribute
- Invalid values at use time fall back to inherited/initial, not earlier rules
Watch out for
- Expecting var() to work inside a media query condition like @media (min-width: var(--bp)).