seedflip
Archive
Mixtapes
Pricing
Sign in

Building a Token-First Design System from Scratch

Most design systems are built backwards. You start with components, pick colors as you go, and eventually extract the repeated values into variables. The result is a token file that mirrors your components rather than defining them. Token-first inverts this: you define the design language before writing a single component. Every visual decision lives in tokens. Components are just containers that reference them. This approach is slower to start and dramatically faster to scale.

Start with curated tokens →

Why components-first fails

The standard workflow looks like this: build a button, pick a blue, hardcode it. Build a card, need a background, pick a gray, hardcode it. Build a form, need a border, pick another gray, hardcode it. After twenty components, you have twenty slightly different interpretations of "gray" and "blue" scattered across your codebase.

Then someone says "let's add dark mode." Now you need to find every hardcoded value, figure out its purpose, and create a variable for it. The extraction process is painful because the values were never designed to be a system. They were designed to make individual components look right in isolation.

Token-first avoids this entirely by defining the system before the components exist.

Step 1: Define the roles

Before picking a single color, list the roles your interface needs. What surfaces exist? What types of text? What interactive elements? What states?

/* Step 1: List the roles (no values yet) */ /* Surfaces */ --color-bg /* Page background */ --color-surface /* Cards, panels */ --color-surface-hover /* Hover state for surfaces */ /* Text */ --color-text /* Primary text */ --color-muted /* Secondary/muted text */ --color-text-inverted /* Text on accent bg */ /* Interactive */ --color-accent /* Primary actions */ --color-accent-hover /* Hover state */ --color-accent-soft /* Subtle backgrounds */ /* Structure */ --color-border /* Dividers, borders */ /* States */ --color-success --color-warning --color-error

This is your semantic contract. Every component in your system will reference these roles. The roles are stable even when the values change. You are designing the vocabulary of your interface before filling in the words.

Step 2: Choose the primitive palette

Now fill in the values. Create a primitive palette that satisfies every role. You need a neutral scale (for backgrounds, surfaces, text, and borders) and a brand scale (for accent states).

/* Step 2: Primitive palette */ :root { /* Neutrals */ --zinc-50: #FAFAFA; --zinc-100: #F4F4F5; --zinc-200: #E4E4E7; --zinc-500: #71717A; --zinc-900: #18181B; --zinc-950: #09090B; /* Brand */ --indigo-400: #818CF8; --indigo-500: #6366F1; --indigo-600: #4F46E5; }

Notice: you only create the primitive entries you need. No full 50-950 scale for every hue. The roles from Step 1 tell you exactly which primitives are required. This prevents palette bloat.

Step 3: Wire primitives to semantics

/* Step 3: Semantic layer (light theme) */ :root { --color-bg: var(--zinc-50); --color-surface: var(--zinc-100); --color-surface-hover: var(--zinc-200); --color-text: var(--zinc-900); --color-muted: var(--zinc-500); --color-text-inverted: #FFFFFF; --color-accent: var(--indigo-500); --color-accent-hover: var(--indigo-600); --color-accent-soft: rgba(99, 102, 241, 0.1); --color-border: var(--zinc-200); --color-success: #22C55E; --color-warning: #EAB308; --color-error: #EF4444; } /* Dark theme override */ .dark { --color-bg: var(--zinc-950); --color-surface: var(--zinc-900); --color-surface-hover: #27272A; --color-text: var(--zinc-50); --color-muted: #A1A1AA; --color-accent: var(--indigo-400); --color-accent-hover: var(--indigo-500); --color-accent-soft: rgba(129, 140, 248, 0.12); --color-border: #27272A; }

Dark mode is already handled. You defined it at the same time as light mode because the semantic layer was designed for it from the start. No retrofitting. No hunting for hardcoded values. This is the payoff of token-first.

Step 4: Add non-color tokens

Color is done. Now define typography, spacing, shadows, and border-radius using the same roles-first approach.

/* Step 4: Complete the token surface */ :root { /* Typography */ --font-heading: 'Space Grotesk', sans-serif; --font-body: 'Inter', sans-serif; --font-mono: 'Geist Mono', monospace; --font-weight-heading: 600; --font-weight-body: 400; --letter-spacing-heading: -0.02em; --line-height-heading: 1.2; --line-height-body: 1.75; /* Spacing (4px grid) */ --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px; --space-6: 24px; --space-8: 32px; --space-12: 48px; --space-16: 64px; /* Shape */ --radius-sm: 6px; --radius-md: 8px; --radius-lg: 12px; --radius-full: 9999px; /* Shadows */ --shadow-sm: 0 1px 2px rgba(0,0,0,0.05); --shadow-md: 0 4px 6px rgba(0,0,0,0.07); --shadow-lg: 0 10px 15px rgba(0,0,0,0.08); }

Step 5: Build components from tokens

Now build components. Every visual property references a token. No raw values in component styles.

/* Button: built entirely from tokens */ .button-primary { background: var(--color-accent); color: var(--color-text-inverted); font-family: var(--font-body); font-weight: 500; padding: var(--space-2) var(--space-4); border-radius: var(--radius-md); border: none; transition: background 100ms ease; } .button-primary:hover { background: var(--color-accent-hover); } /* Card: built entirely from tokens */ .card { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: var(--space-6); box-shadow: var(--shadow-sm); }

These components are theme-proof. Light mode, dark mode, brand swap, complete redesign. None of these require touching the component styles. You change the tokens, and every component updates automatically. For the underlying CSS patterns, see design variables that actually matter.

Step 6: Export and distribute

Your tokens are defined. Your components consume them. Now make the tokens available to your full workflow:

CSS file. The canonical source. Import it in your app entry point.

Tailwind config. Map your tokens to Tailwind utility classes via the @theme directive so you can write bg-surface instead of bg-[var(--color-surface)].

IDE rule files. Copy the token definitions into .cursorrules, CLAUDE.md, or .windsurfrules so AI agents generate code that uses your system.

JSON export. For MCP servers, Figma plugins, or any tooling that consumes tokens programmatically.

The SeedFlip shortcut

Steps 1 through 4 (defining roles, choosing palette, wiring semantics, extending to full surface) is the most tedious part of token-first design. It requires making dozens of interconnected decisions about color relationships, typography pairing, shadow style, and spacing rhythm.

SeedFlip does this for you. Each of the 100+ seeds is a complete token-first system: roles defined, primitives curated, semantics wired, full surface covered. You start at Step 5 (building components) with a production-ready token foundation. Export to CSS variables, Tailwind, or shadcn/ui and start building immediately.

/* SeedFlip: skip steps 1-4 */ /* Export a seed and get the complete system: */ --color-bg: #0A0A0B; --color-surface: #141416; --color-text: #FAFAFA; --color-accent: #818CF8; --font-heading: 'Space Grotesk', sans-serif; --font-body: 'Inter', sans-serif; --radius-md: 12px; --shadow-md: 0 4px 6px rgba(0,0,0,0.3); /* + 30 more tokens covering the full surface */

Token-first is the approach. A curated seed is the accelerator. Together, they give you a design system that scales from day one. For the conceptual foundation, check what are design seeds. For the CSS mechanics, read CSS variables color system.


Token-first is counterintuitive. Defining abstract variables before building concrete components feels backwards. But the inversion pays for itself the first time you add dark mode, switch a brand, or hand your tokens to an AI agent. The system is already there. Every component already references it. You change the tokens and everything updates. That is the power of building on a foundation instead of extracting one after the fact.

Ready to stop guessing?

One flip. Complete design system. Free CSS export.

Start with curated tokens →