Back to Blog
UX/UI DesignNovember 28, 202510 min read

Building a Design System from Zero: Where to Actually Start

DR

Doug Ribeiro

10 min read

At Wonderlabs I built the team's reusable component library and the design system foundation under it, for a credit card PWA serving 50,000+ users, with a three-person front-end team. We fell into the trap that catches most teams. We started with components. It feels productive. You are designing buttons, cards, modals, and shipping artifacts. But without tokens underneath, those components are built on sand.

Why Starting with Components Costs You Later

At Wonderlabs, we designed components for months before formalizing tokens. When we aligned on the token structure, we had to audit every component and replace hardcoded values. It ate more than a sprint and introduced regressions. That is not a hypothetical risk. That is what happens.

The problem is that when you design a button before defining your color tokens, you are making decisions that look like design decisions but are actually data decisions. You pick a blue for the primary action. Which blue? How does it relate to the brand color? What is the hover state derived from? What changes in dark mode? Without tokens, those are arbitrary values baked into the component. When the brand refreshes or a second product needs the system, you are not updating a token. You are hunting through every component for hardcoded values.

The Correct Order

  • 1. Design tokens: raw color scales, typography scales, spacing scale, border radii, elevation, motion. These are your primitive values.
  • 2. Semantic tokens: map raw tokens to intent. --color-brand-blue-500 becomes --color-action-default. This layer handles theming and dark mode.
  • 3. Primitives: base Figma styles and Angular CSS custom properties derived from semantic tokens. No component yet.
  • 4. Base components: Button, Input, Badge, Tag. Each references tokens only. No hardcoded values.
  • 5. Composite components: DataTable, FormGroup, NavigationBar built from base components.
  • 6. Page templates: layout patterns used across the product.

Token Architecture: JSON as the Single Source of Truth

The most important technical decision in a design system is keeping Figma tokens and code tokens in sync. The approach that works is a JSON token file, committed to the repository, that the code treats as the single source of truth. A build step such as Style Dictionary transforms that JSON into CSS custom properties for Angular, Swift constants for iOS, or whatever else the platform needs. How the JSON gets there is a choice with two answers. Author it in code and sync it into Figma with a plugin such as Tokens Studio, or author it in Figma Variables and export. Pick one direction and write it down, because a team that never picks ends up with two sources that have drifted and no way to say which one is right. At Wonderlabs I went the second way, in my last months there in 2023, once Figma Variables existed: tokens defined in Figma Variables, exported as JSON, transformed with Style Dictionary into CSS custom properties consumed by our Angular components, which sat on top of Angular Material. Storybook came in only a little, near the end.

Decide on the token file format before you write the first token, because the two shapes in circulation do not mix inside one file. The example below is Style Dictionary v3, where every token is an object with a value key. Style Dictionary v4 and the DTCG Design Tokens Format Module use $value and $type instead. I used v3 at Wonderlabs and the snippet reflects that. On a new project today I would write the DTCG shape, because that is where the tooling is going, and then check that every plugin and build step in the chain actually reads it.

json
{
  "color": {
    "brand": {
      "blue": {
        "50":  { "value": "#EFF6FF" },
        "100": { "value": "#DBEAFE" },
        "500": { "value": "#3B82F6" },
        "700": { "value": "#1D4ED8" },
        "900": { "value": "#1E3A8A" }
      }
    },
    "action": {
      "default":  { "value": "{color.brand.blue.500}" },
      "hover":    { "value": "{color.brand.blue.700}" },
      "disabled": { "value": "{color.brand.blue.100}" }
    }
  },
  "spacing": {
    "xs":  { "value": "4px" },
    "sm":  { "value": "8px" },
    "md":  { "value": "16px" },
    "lg":  { "value": "24px" },
    "xl":  { "value": "32px" },
    "2xl": { "value": "48px" }
  }
}
Figma Variables tokens.json Style Dictionary CSS / SCSS vars Components

Governance Is Part of the System, Not a Later Problem

A design system without a contribution process becomes a dumping ground within months. Define early who can add tokens, who can add components, and what the review process is. Even on a three-person front-end team this is not optional. A component that ships with a broken accessibility pattern gets used across many features before the bug surfaces. A PR-based workflow with a designer and a developer as required reviewers catches most issues before they propagate.

Versioning matters more than most teams expect when they start. When you change a component API or rename a token, consuming teams need time to migrate. We never hit that at Wonderlabs. Three people, one product, and the library shipped inside the app, so there was exactly one consumer and a rename was a single commit. The day a second product picks the library up, that stops working. That is the part I would set up from the start next time: the library published as a scoped npm package, semantic versioning on every release, and Angular schematics for the breaking migrations. Without it, teams sit on old versions indefinitely and you end up maintaining two versions of the system.

A design system is a product, not a project. It needs maintenance, versioning, documentation, and a team that owns it. Treat it as a one-time build and it will rot within a year.

Angular Integration: CSS Custom Properties and Theme Overrides

The shape I would give it next time, for Angular: the design system published as a scoped npm package and consumed through Angular libraries in a monorepo. Components expose their styles through CSS custom properties mapped from the token file. Consuming apps theme by overriding those custom properties at the host level, never by modifying component code. That is the architecture that makes multi-brand viable. A second brand becomes a second set of token values applied at the host, not a fork of the component code.

If I started that system again, tokens would be the first deliverable. Before a single component exists in Figma, the color scale, semantic token mapping, and spacing scale go into a reviewed token file. Every component decision made after that has a foundation to reference. The rework you avoid is worth far more than the extra time spent on tokens at the start.

References

Design SystemsFigmaUX DesignAngular