Design Systems
A design system is a single source of truth for an interface's reusable design decisions — colours, spacing, typography, and component styles — and in CSS it is commonly built from design tokens stored as custom properties ( var(--token) ) so the whole product stays consistent and easy to re-theme.
Part of the free HTML & CSS course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
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.
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.
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.
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.
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) , never color: #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-primary and --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
Practice quiz
What is a 'design token' in a CSS design system?
- A JavaScript function that styles elements
- A type of HTML element
- A reusable design decision (color, spacing, etc.) stored as a value, commonly a CSS custom property
- A licensing key for a CSS framework
Answer: A reusable design decision (color, spacing, etc.) stored as a value, commonly a CSS custom property. Design tokens are reusable design decisions, typically stored as CSS custom properties.
Where are global design tokens most commonly defined in CSS?
- In the :root pseudo-class
- Inside @media queries
- On the <body> tag's style attribute
- In a separate JSON file only
Answer: In the :root pseudo-class. Global custom properties are usually declared in :root so they are available document-wide.
How do you reference a CSS custom property named --color-primary?
- color: $color-primary;
- color: @color-primary;
- color: custom(color-primary);
- color: var(--color-primary);
Answer: color: var(--color-primary);. You read a custom property with the var() function: var(--color-primary).
What is the standard layering order for design tokens?
- Component → Semantic → Primitive
- Primitive → Semantic → Component
- Semantic → Primitive → Component
- Primitive → Component → Semantic
Answer: Primitive → Semantic → Component. Tokens layer from primitive (raw values) to semantic (role-based) to component (scoped).
Which is an example of a primitive token?
- --blue-500
- --color-primary
- --btn-bg
- --color-danger
Answer: --blue-500. A primitive token like --blue-500 holds a raw value, not a role.
How does var() let you provide a fallback value?
- var(--token || fallback)
- var(--token; fallback)
- var(--token, fallback)
- var(--token: fallback)
Answer: var(--token, fallback). var(--token, fallback) uses the fallback if the token is not defined.
What is the main benefit of a consistent spacing scale?
- It makes the page load faster
- It eliminates arbitrary 'magic number' spacing values and creates visual rhythm
- It forces all elements to the same size
- It removes the need for any CSS
Answer: It eliminates arbitrary 'magic number' spacing values and creates visual rhythm. A spacing scale replaces arbitrary pixel values with consistent tokens, creating visual rhythm.
How can token-based theming (e.g. dark mode) be implemented most easily?
- Rewrite every component's CSS for each theme
- Use a different HTML file per theme
- Add !important to every rule
Overriding token values under [data-theme] re-themes everything that uses those tokens via the cascade.
What does a semantic token like --color-primary typically do?
- Hold a raw hex value used nowhere else
- Give a role-based name that usually references a primitive token
- Define a media query breakpoint
- Store a JavaScript event handler
Answer: Give a role-based name that usually references a primitive token. Semantic tokens give meaning (a role) and usually point to a primitive, e.g. --color-primary: var(--blue-500).
Which is a recommended starting point when building a design system?
- Define 100+ tokens immediately for every imaginable case
- Avoid tokens until the project is finished
- Start with around 10-15 core tokens and add more only when repetition appears
- Use only hardcoded hex values
Answer: Start with around 10-15 core tokens and add more only when repetition appears. Start small with ~10-15 core tokens and expand only when you notice real repetition.
Continue this course
- Previous: Advanced Selectors Deep
- Next: Responsive Images