Master Figma's color style system — creating local styles, using shared libraries, reading color tokens in Dev Mode, and building the complete pipeline from Figma color styles to CSS custom property color tokens.
Color in Figma exists at three levels: raw hex values (any designer can add these without system thinking), local color styles (named presets within the current file), and library variables (connected to a design token system shared across files). The level of rigor at which a design team uses color determines how much drift appears between design and code.
For developers, the most important skill is reading whatever the designer has given you — whether raw hex, named styles, or token variables — and translating it correctly into CSS. This lesson covers all three scenarios and builds toward the ideal: a token-based color workflow where Figma and CSS stay synchronized automatically.
Local color styles appear in the Styles panel under 'Colors'. They are defined within the current Figma file and apply to any element using fill or stroke in that file.
Library color variables appear in the Variables panel. They support modes (light/dark) and can be published to a shared library used across multiple Figma files.
In the Inspect panel:
CSS mapping:
Reading color styles from the Inspect panel:
1/* Three CSS approaches matching three Figma fill types */
2
3/* Raw fill #2563EB → hardcoded (OK for small one-off files) */
4.card-border { border-color: #2563eb; }
5
6/* Local style 'Brand/Primary' → local custom property */
7:root { --color-brand-primary: #2563eb; }
8.card-border { border-color: var(--color-brand-primary); }
9
10/* Library variable 'color/action/primary' → generated token property */
11/* Generated by Style Dictionary from tokens.json */
12:root { --color-action-primary: hsl(220, 90%, 56%); }
13.card-border { border-color: var(--color-action-primary); }Professional Figma color style libraries use a two-level naming convention:
Level 1: Palette (primitives)
'Blue/500', 'Blue/600', 'Gray/100', 'Green/600'
Level 2: Semantic (roles)
'Brand/Primary', 'Brand/Hover', 'Feedback/Success', 'Text/Primary'
Mapping to CSS custom properties:
Slash notation in Figma style names groups them visually in the panel — the slash becomes a hyphen in CSS property names.
Complete color system CSS from a Figma style library:
1/* Figma Color Styles → CSS Custom Properties */
2/* Source: Figma > Styles panel > Colors */
3
4:root {
5 /* Palette layer (primitive colors) */
6 /* Group: Blue */
7 --color-blue-50: hsl(220, 100%, 97%);
8 --color-blue-100: hsl(220, 96%, 93%);
9 --color-blue-500: hsl(220, 90%, 56%);
10 --color-blue-600: hsl(220, 90%, 46%);
11 --color-blue-700: hsl(220, 88%, 37%);
12
13 /* Group: Neutral */
14 --color-neutral-0: #ffffff;
15 --color-neutral-50: hsl(220, 20%, 97%);
16 --color-neutral-100: hsl(220, 20%, 94%);
17 --color-neutral-200: hsl(220, 16%, 90%);
18 --color-neutral-400: hsl(220, 12%, 62%);
19 --color-neutral-600: hsl(220, 16%, 40%);
20 --color-neutral-900: hsl(220, 30%, 10%);
21
22 /* Group: Green */
23 --color-green-50: hsl(142, 72%, 95%);
24 --color-green-600: hsl(142, 72%, 35%);
25
26 /* Group: Red */
27 --color-red-50: hsl(0, 84%, 97%);
28 --color-red-600: hsl(0, 84%, 47%);
29
30 /* Semantic layer (roles) — light mode */
31 --color-brand-primary: var(--color-blue-500);
32 --color-brand-hover: var(--color-blue-600);
33 --color-brand-subtle: var(--color-blue-50);
34
35 --color-feedback-success: var(--color-green-600);
36 --color-feedback-success-subtle: var(--color-green-50);
37 --color-feedback-danger: var(--color-red-600);
38 --color-feedback-danger-subtle: var(--color-red-50);
39
40 --color-surface-default: var(--color-neutral-0);
41 --color-surface-alt: var(--color-neutral-50);
42 --color-surface-raised: var(--color-neutral-0);
43
44 --color-text-primary: var(--color-neutral-900);
45 --color-text-secondary: var(--color-neutral-600);
46 --color-text-muted: var(--color-neutral-400);
47
48 --color-border-default: var(--color-neutral-200);
49 --color-border-strong: var(--color-neutral-400);
50}Two fill types require extra care when reading from the Figma Inspect panel: opacity-modified colors and gradients.
Opacity fills: Figma can apply fill opacity separate from layer opacity. A solid blue fill at 15% opacity is NOT the same as a layer with 15% opacity.
Figma reports: Fill: #2563EB (15%)
Correct CSS: use rgba or the CSS rgb() modern syntax with alpha:
1/* Figma: Fill #2563EB at 15% opacity */
2background: rgb(37 99 235 / 0.15);
3/* Or with HSL: */
4background: hsl(220 90% 56% / 0.15);
5/* NOT: opacity: 0.15 on the element (that affects children too) */Gradient fills: Figma shows gradient type (Linear, Radial, Angular) with color stops, positions, and angles.
Figma linear gradient: 'Linear, 135deg, #1E293B → #0F172A'
1/* Direct CSS translation */
2background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);Figma radial gradient: 'Radial, center 50% 50%, #2563EB (0%) → transparent (100%)'
1background: radial-gradient(circle at 50% 50%, #2563eb 0%, transparent 100%);Figma multi-stop gradient:
'Linear, 90deg, #667EEA (0%) → #764BA2 (100%)'
1background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);Common gradient patterns in UI and their CSS:
1/* Subtle top-to-bottom page gradient */
2.page-hero {
3 background: linear-gradient(180deg,
4 hsl(220, 47%, 11%) 0%,
5 hsl(222, 47%, 8%) 100%
6 );
7}
8
9/* Card shimmer loading state */
10.skeleton {
11 background: linear-gradient(
12 90deg,
13 hsl(220, 20%, 94%) 0%,
14 hsl(220, 20%, 90%) 40%,
15 hsl(220, 20%, 94%) 100%
16 );
17 background-size: 200% 100%;
18 animation: shimmer 1.5s ease-in-out infinite;
19}
20@keyframes shimmer {
21 0% { background-position: 200% 0; }
22 100% { background-position: -200% 0; }
23}The most efficient dark mode implementation uses the same CSS custom property names for both modes — only the values change. Components reference the same token regardless of mode.
Figma dark mode color styles (from the Styles panel with dark mode active):
1Surface/Default: hsl(222, 47%, 11%) → dark bg
2Surface/Alt: hsl(222, 47%, 15%) → dark card
3Text/Primary: hsl(220, 20%, 96%) → light text on dark bg
4Brand/Primary: hsl(220, 90%, 65%) → lighter brand colorCSS implementation:
1/* Light mode: default */
2:root {
3 --color-surface-default: #ffffff;
4 --color-surface-alt: hsl(220, 20%, 97%);
5 --color-text-primary: hsl(220, 30%, 10%);
6 --color-brand-primary: hsl(220, 90%, 56%);
7 --color-border-default: hsl(220, 16%, 90%);
8}
9
10/* Dark mode: override semantic tokens only */
11@media (prefers-color-scheme: dark) {
12 :root {
13 --color-surface-default: hsl(222, 47%, 11%);
14 --color-surface-alt: hsl(222, 47%, 15%);
15 --color-text-primary: hsl(220, 20%, 96%);
16 --color-brand-primary: hsl(220, 90%, 65%); /* lightened for dark bg */
17 --color-border-default: hsl(222, 30%, 24%);
18 }
19}
20
21/* User-controlled theme toggle */
22[data-theme='dark'] {
23 --color-surface-default: hsl(222, 47%, 11%);
24 --color-surface-alt: hsl(222, 47%, 15%);
25 --color-text-primary: hsl(220, 20%, 96%);
26 --color-brand-primary: hsl(220, 90%, 65%);
27 --color-border-default: hsl(222, 30%, 24%);
28}
29
30/* Components: never change — always correct */
31.card {
32 background: var(--color-surface-alt);
33 border: 1px solid var(--color-border-default);
34}
35.card-title {
36 color: var(--color-text-primary);
37}Verifying dark mode contrast (WCAG AA requirement):
A course card component demonstrating the complete color token pipeline from Figma styles to CSS — with both light and dark mode implementations that share identical component CSS.
1<!DOCTYPE html>
2<html lang="en" data-theme="light">
3<head>
4 <meta charset="UTF-8">
5 <title>Color Token System Demo</title>
6 <style>
7 /* Palette (primitives — from Figma color styles, Group: Palette) */
8 :root {
9 --pal-blue-50: hsl(220, 100%, 97%);
10 --pal-blue-500: hsl(220, 90%, 56%);
11 --pal-blue-600: hsl(220, 90%, 46%);
12 --pal-green-50: hsl(142, 72%, 95%);
13 --pal-green-600: hsl(142, 72%, 35%);
14 --pal-neutral-0: #ffffff;
15 --pal-neutral-50: hsl(220, 20%, 97%);
16 --pal-neutral-100: hsl(220, 20%, 94%);
17 --pal-neutral-200: hsl(220, 16%, 90%);
18 --pal-neutral-400: hsl(220, 12%, 62%);
19 --pal-neutral-600: hsl(220, 16%, 40%);
20 --pal-neutral-900: hsl(220, 30%, 10%);
21 }
22
23 /* Semantic tokens — light mode (Figma: Styles panel > Color > Semantic) */
24 :root,
25 [data-theme='light'] {
26 --color-bg: var(--pal-neutral-50);
27 --color-surface: var(--pal-neutral-0);
28 --color-surface-alt: var(--pal-neutral-100);
29 --color-border: var(--pal-neutral-200);
30 --color-text-primary: var(--pal-neutral-900);
31 --color-text-secondary: var(--pal-neutral-600);
32 --color-text-muted: var(--pal-neutral-400);
33 --color-action-primary: var(--pal-blue-500);
34 --color-action-hover: var(--pal-blue-600);
35 --color-action-subtle: var(--pal-blue-50);
36 --color-success: var(--pal-green-600);
37 --color-success-subtle: var(--pal-green-50);
38 }
39
40 /* Semantic tokens — dark mode */
41 @media (prefers-color-scheme: dark) {
42 :root:not([data-theme='light']) {
43 --color-bg: hsl(222, 47%, 9%);
44 --color-surface: hsl(222, 47%, 13%);
45 --color-surface-alt: hsl(222, 47%, 18%);
46 --color-border: hsl(222, 30%, 24%);
47 --color-text-primary: hsl(220, 20%, 96%);
48 --color-text-secondary: hsl(220, 15%, 70%);
49 --color-text-muted: hsl(220, 10%, 52%);
50 --color-action-primary: hsl(220, 90%, 65%);
51 --color-action-hover: hsl(220, 90%, 72%);
52 --color-action-subtle: hsl(220, 60%, 18%);
53 --color-success: hsl(142, 60%, 50%);
54 --color-success-subtle: hsl(142, 40%, 14%);
55 }
56 }
57 [data-theme='dark'] {
58 --color-bg: hsl(222, 47%, 9%);
59 --color-surface: hsl(222, 47%, 13%);
60 --color-surface-alt: hsl(222, 47%, 18%);
61 --color-border: hsl(222, 30%, 24%);
62 --color-text-primary: hsl(220, 20%, 96%);
63 --color-text-secondary: hsl(220, 15%, 70%);
64 --color-text-muted: hsl(220, 10%, 52%);
65 --color-action-primary: hsl(220, 90%, 65%);
66 --color-action-hover: hsl(220, 90%, 72%);
67 --color-action-subtle: hsl(220, 60%, 18%);
68 --color-success: hsl(142, 60%, 50%);
69 --color-success-subtle: hsl(142, 40%, 14%);
70 }
71
72 *, *::before, *::after { box-sizing: border-box; }
73 body { margin: 0; background: var(--color-bg); font-family: system-ui, sans-serif;
74 color: var(--color-text-primary); padding: 2rem;
75 transition: background-color 300ms ease, color 300ms ease; }
76
77 /* Theme toggle */
78 .theme-toggle {
79 display: flex; gap: 0.5rem; margin-bottom: 2rem;
80 }
81 .theme-btn {
82 padding: 0.5rem 1rem; border: 1.5px solid var(--color-border);
83 border-radius: 0.375rem; background: var(--color-surface);
84 color: var(--color-text-secondary); cursor: pointer; font-size: 0.875rem;
85 font-family: inherit; transition: 150ms ease;
86 }
87 .theme-btn[aria-pressed='true'] {
88 border-color: var(--color-action-primary);
89 color: var(--color-action-primary); font-weight: 600;
90 }
91
92 /* Course card — zero hardcoded colors, all semantic tokens */
93 .course-card {
94 background: var(--color-surface);
95 border: 1px solid var(--color-border);
96 border-radius: 0.75rem;
97 overflow: hidden;
98 max-width: 320px;
99 box-shadow: 0 1px 3px rgb(0 0 0 / 0.04);
100 transition: border-color 200ms ease;
101 }
102 .course-card:hover { border-color: var(--color-action-primary); }
103 .course-thumbnail {
104 aspect-ratio: 16/9;
105 background: var(--color-action-subtle);
106 display: flex; align-items: center; justify-content: center;
107 font-size: 2.5rem;
108 }
109 .course-body { padding: 1.25rem; }
110 .badge {
111 display: inline-flex; align-items: center;
112 padding: 0.125rem 0.5rem; border-radius: 9999px;
113 font-size: 0.6875rem; font-weight: 700;
114 text-transform: uppercase; letter-spacing: 0.06em;
115 background: var(--color-success-subtle);
116 color: var(--color-success);
117 margin-bottom: 0.625rem;
118 }
119 .course-title {
120 font-size: 1rem; font-weight: 700;
121 color: var(--color-text-primary); margin: 0 0 0.375rem;
122 }
123 .course-meta {
124 font-size: 0.8125rem; color: var(--color-text-muted); margin: 0 0 1rem;
125 }
126 .course-btn {
127 display: block; width: 100%;
128 padding: 0.625rem 1rem;
129 background: var(--color-action-primary); color: white;
130 border: none; border-radius: 0.5rem; font-size: 0.9375rem;
131 font-weight: 600; cursor: pointer; font-family: inherit;
132 transition: background-color 150ms ease;
133 }
134 .course-btn:hover { background: var(--color-action-hover); }
135 </style>
136</head>
137<body>
138 <div class="theme-toggle">
139 <button class="theme-btn" type="button" aria-pressed="true"
140 onclick="setTheme('light', this)">Light</button>
141 <button class="theme-btn" type="button" aria-pressed="false"
142 onclick="setTheme('dark', this)">Dark</button>
143 </div>
144 <article class="course-card">
145 <div class="course-thumbnail" aria-hidden="true">⚛</div>
146 <div class="course-body">
147 <span class="badge">Beginner</span>
148 <h2 class="course-title">React Fundamentals</h2>
149 <p class="course-meta">24 lessons · 8 hours</p>
150 <button class="course-btn" type="button">Start course</button>
151 </div>
152 </article>
153 <script>
154 function setTheme(theme, btn) {
155 document.documentElement.setAttribute('data-theme', theme);
156 document.querySelectorAll('.theme-btn').forEach(b => b.setAttribute('aria-pressed', 'false'));
157 btn.setAttribute('aria-pressed', 'true');
158 }
159 </script>
160</body>
161</html>The course card contains zero hardcoded colors. Every fill, border, and text color references a semantic CSS custom property (--color-surface, --color-border, --color-text-primary, --color-action-primary, etc.). When the theme toggle calls setTheme('dark'), it adds data-theme='dark' to the html element, which triggers the [data-theme='dark'] selector to override all semantic tokens. The card component CSS does not change at all — it automatically displays correctly in both themes. This is the benefit of the semantic token architecture: components are theme-agnostic by design.
The next lesson covers Figma Effects — drop shadows, inner shadows, and blur effects — and the precise CSS box-shadow and backdrop-filter syntax needed to match them exactly.
Your progress is saved in this browser, on this device only.