Systems Win Races: Design systems, From Foundation to Components
Building a scalable design system with a unified flow: Figma → JSON → Code
A single designer can’t support multiple teams. A single engineer rebuilding components from Figma becomes a bottleneck. But a system where design exports once and engineering consumes systematically scales to teams, products, and organizations.
The system does the work.
People operate it.
The Problem
Most design systems fail for the same structural reason:
They are built from two disconnected starting points.
1. Design-first systems
Designers define components in Figma
Engineers rebuild them in code
Failure mode:
Variants explode, documentation drifts, and visual mismatches become permanent.
The system fails after handoff.
2. Code-first systems
Engineers define components in code
Designers recreate them in Figma
Failure mode:
Tokens live in CSS, types in TypeScript, colors in multiple formats—none fully aligned.
The system fails before alignment.
Root Cause
Both approaches fail for the same reason:
There is no single source of truth.
Foundations (color, typography, motion) exist in multiple places.
Components depend on inconsistent foundations.
Every change requires manual synchronization across tools and teams.
The Core Idea
This system treats foundations and components as structured data, not as design artifacts or code artifacts.
One-way data flow
Figma → Tokens → Code
Design does not hand off files
Design exports a specification
Feedback flows backward through conversation, not code.
If engineering finds an accessibility issue or token problem:
They report it
Design decides
Design re-exports
The system enforces directionality:
Tokens always flow Figma → JSON → Code
Never the reverse.
System Architecture
The system has two explicit layers.
Level 1: Foundations
Foundations are defined once, exported once, and consumed everywhere.
They never depend on components.
Foundations include
Color
Typography
Motion
Visual rules
Color System (Two Levels)
1. Raw Colors
Raw colors are value-only mappings.
They are neutral and intentionally meaningless.
{
"global": {
"colors": {
"raw": {
"gray": {
"0": { "value": "#ffffff", "type": "color" },
"100": { "value": "#adb5bd", "type": "color" },
"900": { "value": "#1a1a1a", "type": "color" }
}
}
}
}
}
Raw colors answer one question:
What is the actual value?
They do not explain intent or usage.
2. Semantic Colors
Semantic colors assign meaning and UI responsibility to raw values.
{
"global": {
"colors": {
"semantic": {
"interactive": {
"primary": {
"background": { "value": "{global.colors.raw.blue-500}", "type": "color" },
"foreground": { "value": "{global.colors.raw.gray-0}", "type": "color" },
"border": { "value": "{global.colors.raw.blue-700}", "type": "color" }
}
},
"surface": {
"primary": {
"background": { "value": "{global.colors.raw.gray-0}", "type": "color" },
"foreground": { "value": "{global.colors.raw.gray-900}", "type": "color" }
}
}
}
}
}
}
Semantic tokens separate:
Intent → primary, secondary, disabled, positive, negative
Placement → background, foreground, border, state layer, focus ring
A single intent produces multiple responsibilities.
Example:
A primary interactive color defines:
Button background
Text color on that background
Icon color
Border color
Theme changes remap raw colors.
Component logic stays untouched.
Typography
Typography tokens define everything needed for implementation—explicitly.
{
"global": {
"typography": {
"body-md": {
"fontSize": { "value": "14", "type": "fontSize" },
"fontWeight": { "value": "400", "type": "fontWeight" },
"lineHeight": { "value": "1.5", "type": "lineHeight" }
}
}
}
}
No implied defaults.
No interpretation gap.
Motion
Motion is a foundation, not a per-component decision.
{
"global": {
"motion": {
"duration-short": { "value": "150ms", "type": "duration" },
"easing-out": {
"value": "cubic-bezier(0, 0, 0.2, 1)",
"type": "cubic-bezier"
}
}
}
}
Consistency comes from reuse, not guidelines.
Level 2: Components
Components sit on top of foundations.
They never define raw values.
Each component declares:
Properties — configurable inputs
States — hover, pressed, focus, disabled
Token mapping — how semantic tokens apply to UI parts
Button Example
{
"components": {
"button": {
"properties": {
"size": { "type": "enum", "values": ["xs", "md", "lg"] },
"style": {
"type": "enum",
"values": ["filled", "outlined", "ghost", "link"]
},
"leadingIcon": { "type": "boolean" },
"trailingIcon": { "type": "boolean" },
"iconOnly": { "type": "boolean" }
},
"tokens": {
"filled-primary-default": {
"background": {
"value": "{global.colors.semantic.interactive.primary.background}",
"type": "color"
},
"foreground": {
"value": "{global.colors.semantic.interactive.primary.foreground}",
"type": "color"
}
}
},
"sizing": {
"md": {
"padding": { "value": "10px 16px", "type": "spacing" }
}
}
}
}
}
Each token maps to a UI responsibility:
Background
Foreground
Border
State layer
Focus ring
No hard-coded values.
No component-level color decisions.
The Flow
1. Design (Figma)
Designers:
Define foundations in Variables
Build components using semantic tokens
Map instance properties
2. Export (Figma → JSON)
All variables export into a single schema.
This file is the source of truth.
3. Implementation (JSON → Code)
Engineering consumes the schema directly.
Tokens resolve programmatically
Components are type-safe by construction
Invalid combinations don’t compile
No guessing.
No manual syncing.
Operational Reality
Theme Switching
Semantic tokens stay stable
Raw color mappings change
Themes are different JSON exports
Versioning
The schema is versioned like an API.
Breaking changes require major versions
Migrations are explicit
Deployment
Token changes follow the same pipeline as code:
Design → JSON → Git → CI → Release
No separate “token deployment.”
Tooling
Figma Variables
JSON Schema
Style Dictionary
TypeScript
Tailwind CSS
Why This Holds Up
Single Source of Truth
Foundations exist once.
Everything references them.
Clear Separation
Raw = value
Semantic = intent
Component = usage
Each layer answers a different question.
Predictable Scaling
New themes remap raw colors
New sizes add sizing tokens
Nothing else breaks
Type Safety
Design decisions are enforced at build time and runtime.
Final Thought
Most systems try to keep design and code aligned by maintaining two systems.
This treats them as one system, expressed as data.
Design doesn’t ship files.
It ships specifications.
Sobhan Rabbani

