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.

Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.

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.

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.

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.

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.

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.

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.

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.

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.