Building Design Systems with CSS Variables
Lesson 26 โข Advanced Track
What You'll Learn
๐ก Think of It Like This
A design system is like a LEGO set โ each brick (token) has a specific size, color, and shape. Instead of sculpting every element from scratch, you assemble consistent UIs by snapping tokens together. Changing a token is like swapping the color of a LEGO brick: every structure using it updates at once.
Understanding Design Systems
A design system is a collection of reusable decisions โ colors, spacing, typography, component patterns โ encoded as CSS custom properties (also called "design tokens"). Instead of scattering #1976D2 across 50 files, you define it once as --color-primary and reference it everywhere.
This approach has three major benefits: consistency (every button uses the same blue), maintainability (change the blue once, everything updates), and theming (swap all tokens at once for dark mode or branding).
Design tokens are organized in layers. Primitive tokens are raw values like --blue-500: #1976D2. Semantic tokens give them meaning: --color-primary: var(--blue-500). Component tokens scope to specific elements: --btn-bg: var(--color-primary). This layering makes large codebases manageable.
Token Naming Convention
| Layer | Example | Purpose | When to Use |
|---|---|---|---|
| Primitive | --blue-500 | Raw color value | Never directly in components |
| Semantic | --color-primary | Role-based reference | In most CSS rules |
| Component | --btn-bg | Scoped to a component | For complex component variants |
| Spacing | --space-4 | Consistent whitespace | All padding, margin, gap values |
Step 1: Define Your Tokens
Start by defining all your visual decisions in :root. This includes colors, spacing, typography, and border radii. The editor below shows a complete token set with a visual swatch grid.
Design Tokens in Action
<!DOCTYPE html>
<html>
<head>
<style>
:root {
--color-primary: #1976D2;
--color-primary-light: #BBDEFB;
--color-primary-dark: #0D47A1;
--color-success: #2E7D32;
--color-danger: #C62828;
--color-text: #212121;
--color-text-muted: #757575;
--color-bg: #FAFAFA;
--color-surface: #FFFFFF;
--space-xs: 4px;
--space-sm: 8px;
--space-md: 16px;
...Step 2: Build Components with Tokens
Once tokens are defined, every component references them instead of hardcoded values. Notice how .btn-primary uses var(--color-primary) and .card uses var(--color-surface). If you change --color-primary from blue to purple, every button and accent updates automatically.
Reusable Components
<!DOCTYPE html>
<html>
<head>
<style>
:root {
--color-primary: #1976D2;
--color-primary-hover: #1565C0;
--color-surface: #FFFFFF;
--color-text: #212121;
--color-text-muted: #757575;
--color-border: #E0E0E0;
--space-sm: 8px;
--space-md: 16px;
--space-lg: 24px;
--radius-md: 8px;
--font-body: system-ui, sans-serif;
}
body { font-family: var
...Step 3: Establish a Spacing Scale
A spacing scale eliminates "magic numbers" โ those arbitrary pixel values like padding: 13px that creep into CSS. Instead, every spacing value comes from a predefined scale (4, 8, 12, 16, 24, 32, 48, 64px). This creates visual rhythm and makes your layouts feel cohesive.
Spacing Scale System
<!DOCTYPE html>
<html>
<head>
<style>
:root {
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-8: 32px;
--space-10: 40px;
--space-12: 48px;
--space-16: 64px;
--color-primary: #1976D2;
--color-surface: #fff;
--color-border: #e0e0e0;
--color-text: #212121;
...Step 4: Add Theme Switching
The ultimate payoff of a token-based system is effortless theming. By overriding tokens inside a [data-theme] attribute selector, you can switch every color in your UI with a single attribute change. No class toggling, no JavaScript manipulation of individual elements โ just change the data attribute and the cascade does the rest.
Theme Switching
<!DOCTYPE html>
<html>
<head>
<style>
:root {
--bg: #FFFFFF;
--text: #1a1a2e;
--primary: #0f3460;
--accent: #e94560;
--surface: #f0f0f0;
}
[data-theme="dark"] {
--bg: #1a1a2e;
--text: #e0e0e0;
--primary: #4fc3f7;
--accent: #ff6b6b;
--surface: #16213e;
}
[data-theme="forest"] {
--bg: #f1f8e9;
--text: #1b5e20;
...When to Use Design Systems
- Any project with more than one page โ Even small sites benefit from centralized tokens for consistency.
- Team projects โ Tokens are a shared vocabulary that prevents "which blue?" conversations.
- Products with theming needs โ Dark mode, white-label branding, or seasonal themes become trivial.
- Not needed for: One-off prototypes or single-page experiments where speed matters more than maintainability.
โ ๏ธ Common Mistakes
- Too many tokens too early โ Start with 10โ15 core tokens. Add more only when you notice repetition across 3+ places.
- Using raw values in components โ Always reference tokens:
color: var(--color-primary), nevercolor: #1976D2. - Forgetting fallbacks โ
var(--color-primary, blue)prevents breakage if a token is missing. - Inconsistent naming โ Pick one convention and stick with it. Don't mix
--clr-primaryand--color-main. - Skipping the spacing scale โ Without it, every developer invents their own padding values, creating visual inconsistency.
- Not documenting tokens โ Create a reference page (like the swatch grid above) so the team knows what's available.
๐ Lesson Complete
- โ
Design tokens centralize all visual decisions in
:root - โ Primitive โ Semantic โ Component is the standard token layering
- โ A spacing scale (4, 8, 16, 24, 32โฆ) eliminates magic numbers
- โ Components use only tokens, making themes a simple override
- โ
[data-theme]attribute enables instant multi-theme switching - โ Start small: 10-15 tokens cover most projects initially
- โ
Always provide fallback values with
var(--token, fallback) - โ Document your tokens visually so the team has a shared reference
Sign up for free to track which lessons you've completed and get learning reminders.