What is it?
CSS custom properties (variables) let you define a value once and reuse
it everywhere — --color-primary, --space-md, --radius-card. Unlike
Sass variables, they live in the cascade. Change one at runtime, and
everything that uses it updates instantly. That's the whole basis of
modern theming.
Why it matters
Design systems live or die on tokens. Hardcoded #FF477E scattered
across 200 files is an unfindable bug factory. One --brand-pink
defined at :root is a single line of CSS to change — and dark mode,
brand variants, and per-section themes become free.
What to learn
- Defining variables on
:root(global) vs nested (scoped) - The cascade applies: variables override per-selector
var(--name, fallback)for safe defaults- Dark mode patterns:
prefers-color-scheme+[data-theme="dark"] - Token categories: color, space, radius, shadow, type — and naming them
color-mix()and modern color spaces (oklch) for derived values
Common pitfall
Defining tokens in JavaScript and writing them to CSS with React state. The platform already has variables. Use them. JS-driven theming makes SSR look weird (the wrong theme paints first) and adds runtime cost for something CSS does at zero.
Resources
Primary (free):
- MDN — Using CSS custom properties · docs
- web.dev — CSS variables · docs
- Adam Argyle — Building a theme switch · article
Practice
Take a small component (a card, a header, a button). Replace every
hardcoded color, spacing, and radius with a CSS variable defined at
:root. Then duplicate the :root block under [data-theme="dark"]
with dark values. Toggle the attribute on <html> and verify the
component reskins without a single component-level change. Done when
you can swap themes by toggling one attribute.
Outcomes
- Define a token system (color / space / radius) using
:rootvariables. - Override tokens at scoped selectors (sections, components, dark mode).
- Add a working dark mode that respects
prefers-color-scheme. - Refactor a hardcoded component to use tokens without changing its output.