Why Custom Properties?
CSS custom properties (often called CSS variables) let you store values and reuse them throughout your stylesheet. Unlike preprocessor variables ($ in Sass or Less), custom properties are live — they cascade, can be overridden at any level, and can be changed at runtime.
"Custom properties are to design tokens what functions are to programming — they give you a single source of truth that ripples through the entire system."
When you define a value once as a custom property, every reference to it updates automatically when the property changes. This is the foundation of a maintainable design system.
The oklch Colour Space
Traditional rgb and hsl colour spaces are device-dependent and perceptually uneven. The oklch colour space, introduced in CSS Color Level 4, is designed for human perception — a 10% lightness shift looks like a 10% lightness shift, regardless of the hue.
/* Traditional RGB — hard to reason about */
--accent: #4a6cf7;
/* oklch — perceptually uniform, easy to tweak */
--accent: oklch(65% 0.15 250);
The three components of oklch are:
- L — Lightness (0% to 100%)
- C — Chroma or saturation (0 to ~0.4)
- H — Hue (0 to 360 degrees)
Why This Matters for Design Systems
Because oklch uses a polar coordinate system (like HSL), you can rotate hues while keeping perceptual lightness constant. This makes generating colour scales, accent colours, and dark mode variants straightforward.
:root {
--palette-hue: 250; /* blue-violet */
--palette-chroma: 0.15;
--accent: oklch(65% var(--palette-chroma) var(--palette-hue));
--accent-hover: oklch(72% var(--palette-chroma) var(--palette-hue));
}
Building a Token System
A design token system organises custom properties into semantic layers:
- Primitives — raw values (sizes, colours, fonts)
- Aliases — semantic names mapped to primitives
- Component tokens — role-specific values
For example, a spacing scale starts with raw sizes:
--size-1: 0.25rem; /* 4px */
--size-2: 0.5rem; /* 8px */
--size-3: 1rem; /* 16px */
Then semantic aliases map those sizes to their roles:
--space-1: var(--size-1); /* tiny gap */
--space-2: var(--size-2); /* tight gap */
--space-4: var(--size-3); /* standard gap */
Live Reload in the Browser
One of the superpowers of custom properties is live editing. Open your browser's dev tools, change a token value in :root, and watch every element that uses it update in real time.
/* Change this in dev tools and see the whole page shift */
:root {
--palette-hue: 200; /* shift from blue-violet to blue */
}
Common Pitfalls
Custom properties are powerful, but they come with some gotchas:
- Fallback values — always provide a fallback:
var(--custom, fallback) - Inheritance — custom properties inherit by default. Use
@propertyfor controlled behaviour. - Animation — not all custom property types can be animated without
@property - Performance — thousands of unique custom properties in a single scope can slow repaints
Conclusion
CSS custom properties, combined with the oklch colour space, give you everything you need for a modern design token system — no preprocessor required. It's more verbose in places, but the trade-off is runtime flexibility, browser-native live editing, and zero build dependencies.
Footnotes
- The
oklchspecification is part of CSS Color Module Level 4. Read the spec. - Browser support for oklch is excellent — all modern browsers support it as of 2024. Check CanIUse.