Build a complete design system in Figma from scratch — defining color and spacing tokens, creating reusable components with variants, organizing a library, and publishing it for team use.
A design system is the shared vocabulary between design and engineering. In Figma, it takes the form of a library file: color and spacing tokens in the Variables panel, reusable components organized by category, and documentation that explains usage rules. When this library is well-built, every new feature starts from a consistent foundation — reducing design decisions, reducing implementation time, and eliminating the inconsistencies that accumulate in ad-hoc codebases.
This lesson walks through building a minimal but production-quality design system in Figma, with every step paired with its CSS equivalent.
A production Figma design system file uses this page structure:
File naming convention:
CSS equivalent of the design system file structure:
1src/
2 styles/
3 tokens.css (Foundations)
4 reset.css (global reset)
5 typography.css (global type styles)
6 components/
7 atoms/ (Components)
8 molecules/ (Patterns)
9 organisms/ (Templates)
10 pages/ (specific page styles)Building the token foundation in Figma Variables:
Step 1 — Create the Variables panel:
Step 2 — Add primitive color variables:
Step 3 — Add semantic color collection:
Step 4 — Add spacing collection:
Step 5 — Apply to components:
Exported tokens.json (Tokens Studio output):
1{
2 "color": {
3 "primitive": {
4 "blue": {
5 "500": { "value": "hsl(220, 90%, 56%)", "type": "color" }
6 }
7 },
8 "action": {
9 "primary": {
10 "value": "{color.primitive.blue.500}",
11 "type": "color"
12 }
13 }
14 },
15 "spacing": {
16 "4": { "value": "1rem", "type": "dimension" }
17 }
18}Generated CSS (Style Dictionary):
1:root {
2 --color-primitive-blue-500: hsl(220, 90%, 56%);
3 --color-action-primary: var(--color-primitive-blue-500);
4 --spacing-4: 1rem;
5}Building the Button component in Figma:
Step 1 — Create the base frame:
Step 2 — Add the label text:
Step 3 — Create the component:
Step 4 — Add properties:
Step 5 — Add variants:
Step 6 — Document:
CSS output of this component:
1/* Generated from Button Figma component system */
2.btn {
3 display: inline-flex;
4 align-items: center;
5 justify-content: center;
6 gap: 0.375rem; /* 6px gap */
7 padding: 0.625rem 1.25rem; /* 10px 20px */
8 height: 2.75rem; /* 44px */
9 border-radius: 0.5rem; /* 8px */
10 font-size: 0.9375rem; /* 15px */
11 font-weight: 600;
12 font-family: inherit;
13 cursor: pointer;
14 border: 1.5px solid transparent;
15 transition:
16 background-color 150ms ease,
17 border-color 150ms ease;
18 text-decoration: none;
19}
20.btn-primary {
21 background: var(--color-action-primary);
22 border-color: var(--color-action-primary);
23 color: white;
24}
25.btn-primary:hover {
26 background: var(--color-action-hover);
27 border-color: var(--color-action-hover);
28}
29.btn-primary:focus-visible {
30 outline: none;
31 box-shadow: 0 0 0 3px white, 0 0 0 5px var(--color-action-primary);
32}
33.btn-primary:active {
34 background: var(--color-action-active);
35}
36.btn-primary[disabled] {
37 opacity: 0.4;
38 cursor: not-allowed;
39 pointer-events: none;
40}Publishing the library makes components available across all Figma files in your organization or team.
Publishing process:
Consuming in another file:
Library update workflow:
For developers, library updates are significant: they mean component specs may have changed and CSS may need updating.
Library update audit workflow:
1# When library update is accepted:
2# 1. In Figma: compare current vs updated component
3# 2. Note changed properties
4# 3. Update CSS variables and component styles
5# 4. Run visual regression tests
6
7# Example: Button border-radius changed from 8px to 10px in library update
8# Old CSS:
9.btn { border-radius: 0.5rem; } /* 8px */
10
11# New CSS after library update:
12.btn { border-radius: 0.625rem; } /* 10px */
13
14# If using a design token:
15# Only the token value changes — no component CSS change needed
16:root {
17 --radius-md: 0.5rem; /* old */
18 --radius-md: 0.625rem; /* new — updated from library */
19}
20/* .btn { border-radius: var(--radius-md); } unchanged */This is the key argument for token-based CSS: when a design system token changes, only tokens.css updates — no component files need to change.
A complete minimal design system in CSS — tokens, four atom components, and one molecule — structured to mirror a Figma design system library.
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>Design System Preview</title>
6 <style>
7 /* =====================
8 TOKENS (Foundations)
9 ===================== */
10 :root {
11 /* Primitive colors */
12 --prim-blue-100: hsl(220, 96%, 93%);
13 --prim-blue-500: hsl(220, 90%, 56%);
14 --prim-blue-600: hsl(220, 90%, 46%);
15 --prim-blue-700: hsl(220, 88%, 37%);
16 --prim-green-50: hsl(142, 72%, 95%);
17 --prim-green-600: hsl(142, 72%, 35%);
18 --prim-red-50: hsl(0, 84%, 97%);
19 --prim-red-600: hsl(0, 84%, 47%);
20 --prim-gray-50: hsl(220, 20%, 97%);
21 --prim-gray-100: hsl(220, 20%, 94%);
22 --prim-gray-200: hsl(220, 16%, 90%);
23 --prim-gray-400: hsl(220, 12%, 62%);
24 --prim-gray-600: hsl(220, 16%, 40%);
25 --prim-gray-900: hsl(220, 30%, 10%);
26 /* Semantic colors */
27 --color-action-primary: var(--prim-blue-500);
28 --color-action-hover: var(--prim-blue-600);
29 --color-action-active: var(--prim-blue-700);
30 --color-action-subtle: var(--prim-blue-100);
31 --color-success: var(--prim-green-600);
32 --color-success-subtle: var(--prim-green-50);
33 --color-danger: var(--prim-red-600);
34 --color-danger-subtle: var(--prim-red-50);
35 --color-surface: white;
36 --color-surface-alt: var(--prim-gray-50);
37 --color-border: var(--prim-gray-200);
38 --color-text-primary: var(--prim-gray-900);
39 --color-text-secondary: var(--prim-gray-600);
40 --color-text-muted: var(--prim-gray-400);
41 /* Spacing */
42 --sp-1: .25rem; --sp-2: .5rem; --sp-3: .75rem;
43 --sp-4: 1rem; --sp-5: 1.25rem; --sp-6: 1.5rem;
44 /* Radius */
45 --r-sm: .375rem; --r-md: .5rem; --r-lg: .75rem; --r-pill: 9999px;
46 /* Shadow */
47 --sh-card: 0 1px 3px rgb(0 0 0/.04), 0 1px 2px rgb(0 0 0/.03);
48 }
49 *, *::before, *::after { box-sizing: border-box; }
50 body { margin: 0; font-family: system-ui, sans-serif;
51 background: var(--color-surface-alt); color: var(--color-text-primary);
52 padding: 2rem; }
53 h2 { font-size: .75rem; font-weight: 700; text-transform: uppercase;
54 letter-spacing: .08em; color: var(--color-text-muted); margin: 2rem 0 1rem; }
55 .row { display: flex; gap: .75rem; flex-wrap: wrap; align-items: center; margin-bottom: 1.5rem; }
56
57 /* ============
58 ATOMS: Button
59 ============ */
60 .btn {
61 display: inline-flex; align-items: center; justify-content: center;
62 gap: var(--sp-2); padding: var(--sp-3) var(--sp-6);
63 border-radius: var(--r-md); font-size: .9375rem; font-weight: 600;
64 font-family: inherit; cursor: pointer; border: 1.5px solid transparent;
65 transition: 150ms ease; text-decoration: none; white-space: nowrap;
66 }
67 .btn-primary { background: var(--color-action-primary); color: white; border-color: var(--color-action-primary); }
68 .btn-primary:hover { background: var(--color-action-hover); border-color: var(--color-action-hover); }
69 .btn-secondary { background: var(--color-surface); color: var(--color-action-primary); border-color: var(--color-action-primary); }
70 .btn-secondary:hover { background: var(--color-action-subtle); }
71 .btn-ghost { background: transparent; color: var(--color-text-secondary); }
72 .btn-ghost:hover { background: var(--color-surface); color: var(--color-text-primary); }
73 .btn-danger { background: var(--color-surface); color: var(--color-danger); border-color: var(--color-danger); }
74 .btn-danger:hover { background: var(--color-danger-subtle); }
75 .btn[disabled] { opacity: .4; cursor: not-allowed; pointer-events: none; }
76
77 /* ===========
78 ATOMS: Badge
79 =========== */
80 .badge { display: inline-flex; align-items: center; padding: .1875rem .5rem;
81 border-radius: var(--r-pill); font-size: .6875rem; font-weight: 700;
82 text-transform: uppercase; letter-spacing: .08em; white-space: nowrap; }
83 .badge-success { background: var(--color-success-subtle); color: var(--color-success); }
84 .badge-danger { background: var(--color-danger-subtle); color: var(--color-danger); }
85 .badge-brand { background: var(--color-action-subtle); color: var(--color-action-primary); }
86
87 /* ============
88 ATOMS: Input
89 ============ */
90 .field {
91 display: block; width: 100%; padding: var(--sp-3) var(--sp-4);
92 border: 1.5px solid var(--color-border); border-radius: var(--r-md);
93 font-size: 1rem; font-family: inherit; color: var(--color-text-primary);
94 background: var(--color-surface); transition: 150ms ease; outline: none;
95 }
96 .field:focus { border-color: var(--color-action-primary);
97 box-shadow: 0 0 0 3px hsl(220 90% 56%/.12); }
98 .field[aria-invalid='true'] { border-color: var(--color-danger);
99 box-shadow: 0 0 0 3px hsl(0 84% 47%/.1); }
100
101 /* ================
102 MOLECULE: Card
103 ================ */
104 .card { background: var(--color-surface); border: 1px solid var(--color-border);
105 border-radius: var(--r-lg); box-shadow: var(--sh-card); overflow: hidden;
106 display: flex; flex-direction: column; transition: 200ms ease; }
107 .card:hover { box-shadow: 0 4px 12px rgb(0 0 0/.08); border-color: hsl(220 90% 56%/.3); }
108 .card-thumb { aspect-ratio: 16/9; overflow: hidden; background: var(--color-action-subtle);
109 display: flex; align-items: center; justify-content: center; font-size: 2rem; }
110 .card-body { padding: var(--sp-5); display: flex; flex-direction: column; gap: var(--sp-3); flex: 1; }
111 .card-title { font-size: 1rem; font-weight: 700; color: var(--color-text-primary); margin: 0;
112 line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2;
113 -webkit-box-orient: vertical; overflow: hidden; }
114 .card-meta { font-size: .8125rem; color: var(--color-text-muted); margin: 0; }
115 .card .btn { width: 100%; display: flex; margin-top: auto; }
116 </style>
117</head>
118<body>
119 <h2>Buttons</h2>
120 <div class="row">
121 <button class="btn btn-primary" type="button">Primary</button>
122 <button class="btn btn-secondary" type="button">Secondary</button>
123 <button class="btn btn-ghost" type="button">Ghost</button>
124 <button class="btn btn-danger" type="button">Danger</button>
125 <button class="btn btn-primary" type="button" disabled>Disabled</button>
126 </div>
127 <h2>Badges</h2>
128 <div class="row">
129 <span class="badge badge-success">Beginner</span>
130 <span class="badge badge-brand">New</span>
131 <span class="badge badge-danger">Advanced</span>
132 </div>
133 <h2>Inputs</h2>
134 <div style="max-width:320px; display:flex; flex-direction:column; gap:.75rem;">
135 <input class="field" type="text" placeholder="Default field">
136 <input class="field" type="text" value="Filled field" aria-invalid="false">
137 <input class="field" type="text" value="Error field" aria-invalid="true">
138 </div>
139 <h2>Card (Molecule)</h2>
140 <div style="max-width:300px;">
141 <article class="card">
142 <div class="card-thumb" aria-hidden="true">⚛</div>
143 <div class="card-body">
144 <span class="badge badge-success">Beginner</span>
145 <h3 class="card-title">React Fundamentals: Hooks, Context, and Performance</h3>
146 <p class="card-meta">24 lessons · 8 hours</p>
147 <button class="btn btn-primary" type="button">Enroll free</button>
148 </div>
149 </article>
150 </div>
151</body>
152</html>This mini design system demonstrates the three-tier architecture: primitive tokens (--prim-*) are defined first and never referenced in components. Semantic tokens (--color-action-primary, --sp-4, --r-md) reference primitives and are what components use. Atoms (Button, Badge, Input, Card shell) are self-contained. The Card molecule uses .btn by overriding width and display — a legitimate molecule-level override. If the brand color changes from blue-500 to blue-600, updating --color-action-primary propagates to all buttons, badges, and focused inputs simultaneously.
The next lesson covers the Figma REST API and Tokens Studio integration — automating token export, building CI pipelines, and connecting Figma changes directly to code deployments.
Your progress is saved in this browser, on this device only.