Master the complete workflow for converting a Figma screen to production React components — component mapping, CSS Modules integration, prop type derivation from variants, and systematic design QA.
Translating a Figma design to React components is the daily task of most frontend developers working in product teams. The quality of the translation — how accurately the implementation matches the design — determines whether the product looks professionally designed or 'close enough.'
This lesson establishes a systematic 5-step workflow for Figma-to-React translation: read the layer hierarchy, plan component boundaries, derive TypeScript props from Figma properties, write CSS from the Inspect panel, and write JSX from the semantic HTML decisions. Following this workflow consistently produces zero-drift implementations on the first attempt.
Step 1 — Read the Figma layers panel:
Before writing any code, spend 5 minutes reading the complete layers panel hierarchy. This reveals:
Figma layers for a course listing page:
1Page: Course Listing
2└── Frame: CourseListingPage
3 ├── Component: Navigation
4 └── Frame: Main (Auto Layout vertical)
5 ├── Frame: Hero (Auto Layout vertical)
6 │ ├── Text: heading
7 │ └── Text: subheading
8 └── Frame: CourseGrid (Auto Layout horizontal, wrap)
9 ├── Component: CourseCard (x3 instances)
10 └── Component: CourseCardStep 2 — Plan component boundaries:
Every Figma Component instance = one React component. Frames that repeat = array + .map(). Auto Layout = flex/grid container.
1// Component tree derived from Figma layers
2// CourseListingPage.tsx
3export function CourseListingPage({ courses }) {
4 return (
5 <div className={styles.page}>
6 <Navigation /> {/* Figma: Component: Navigation */}
7 <main className={styles.main}> {/* Figma: Frame: Main */}
8 <section className={styles.hero}> {/* Figma: Frame: Hero */}
9 <h1 className={styles.heading}>Learn to build</h1>
10 <p className={styles.subheading}>Master modern development</p>
11 </section>
12 <div className={styles.grid}> {/* Figma: Frame: CourseGrid */}
13 {courses.map(course => ( {/* Figma: repeated CourseCard instances */}
14 <CourseCard key={course.id} {...course} />
15 ))}
16 </div>
17 </main>
18 </div>
19 );
20}Figma component properties (visible in Dev Mode Inspect panel under 'Properties') become TypeScript prop types. The mapping is direct:
Figma CourseCard component properties:
1Properties:
2 title: Text (string)
3 instructor: Text (string)
4 thumbnailUrl: Text (string, URL)
5 duration: Text (string, e.g. '8 hours')
6 lessonCount: Text (string, e.g. '24 lessons')
7 difficulty: Text (Beginner | Intermediate | Advanced)
8 isEnrolled: Boolean (default: false)
9 isFeatured: Boolean (default: false)
10 variant: Instance Swap (default | compact | minimal)TypeScript interface derived from Figma properties:
1// types/course.ts
2export type DifficultyLevel = 'Beginner' | 'Intermediate' | 'Advanced';
3export type CardVariant = 'default' | 'compact' | 'minimal';
4
5export interface CourseCardProps {
6 title: string; // Figma Text property
7 instructor: string; // Figma Text property
8 thumbnailUrl: string; // Figma Text property (URL)
9 duration: string; // Figma Text property ('8 hours')
10 lessonCount: string; // Figma Text property ('24 lessons')
11 difficulty: DifficultyLevel; // Figma Text property (constrained values)
12 isEnrolled?: boolean; // Figma Boolean property, default false
13 isFeatured?: boolean; // Figma Boolean property, default false
14 variant?: CardVariant; // Figma Instance Swap property
15 onEnroll?: () => void; // Developer addition: not in Figma (interaction)
16 className?: string; // Developer addition: style override support
17}Every prop type traces to a specific Figma component property. The TypeScript type acts as documentation: when a developer sees CourseCardProps, they can find the corresponding Figma property in 30 seconds.
CSS Modules provide component-scoped styles that mirror the Figma layer names. Use the Figma layer name as the CSS class name:
Figma layer: 'CourseCard/Body' → CSS: .body
Figma layer: 'CourseCard/Title' → CSS: .title
Figma layer: 'CourseCard/Thumbnail' → CSS: .thumbnail
1/* CourseCard.module.css */
2/* Every rule traces to a Figma Inspect panel reading */
3
4/* Outer frame: Auto Layout V, 1px #E5E7EB border, 12px radius, shadow-card */
5.card {
6 display: flex;
7 flex-direction: column;
8 border: 1px solid var(--color-border);
9 border-radius: 0.75rem; /* 12px */
10 box-shadow: var(--sh-card);
11 background: var(--color-surface);
12 overflow: hidden;
13 transition: border-color 200ms ease, box-shadow 200ms ease;
14}
15.card:hover {
16 border-color: hsl(220 90% 56% / 0.3);
17 box-shadow: 0 4px 12px rgb(0 0 0 / 0.08);
18}
19
20/* featured variant: 2px brand border */
21.cardFeatured {
22 border: 2px solid var(--color-action-primary);
23 box-shadow: 0 4px 16px hsl(220 90% 56% / 0.1);
24}
25
26/* Thumbnail: 16:9, img fills frame */
27.thumbnail {
28 aspect-ratio: 16 / 9;
29 overflow: hidden;
30 flex-shrink: 0;
31 background: var(--color-action-subtle);
32}
33.thumbnail img {
34 width: 100%;
35 height: 100%;
36 object-fit: cover;
37}
38
39/* Body: Auto Layout V, 20px padding, 12px gap */
40.body {
41 display: flex;
42 flex-direction: column;
43 padding: 1.25rem; /* 20px */
44 gap: 0.75rem; /* 12px */
45 flex: 1;
46}
47
48/* Title: Inter 700 17px, #0F172A, lh 1.353, 2-line clamp */
49.title {
50 font-size: 1.0625rem; /* 17px */
51 font-weight: 700;
52 line-height: 1.353; /* 23/17 */
53 color: var(--color-text-primary);
54 margin: 0;
55 display: -webkit-box;
56 -webkit-line-clamp: 2;
57 -webkit-box-orient: vertical;
58 overflow: hidden;
59}
60
61/* Meta: Inter 400 13px, #6B7280 */
62.meta {
63 font-size: 0.8125rem; /* 13px */
64 color: var(--color-text-muted);
65 margin: 0;
66}
67
68/* CTA: full-width, variant of .btn atom */
69.cta {
70 width: 100%;
71 display: flex;
72 margin-top: auto; /* pushes to card bottom */
73}With props typed and CSS written, JSX assembles the component:
1// CourseCard.tsx
2import styles from './CourseCard.module.css';
3import { Badge } from '../atoms/Badge';
4import { Button } from '../atoms/Button';
5import type { CourseCardProps } from '../../types/course';
6
7export function CourseCard({
8 title,
9 instructor,
10 thumbnailUrl,
11 duration,
12 lessonCount,
13 difficulty,
14 isEnrolled = false,
15 isFeatured = false,
16 variant = 'default',
17 onEnroll,
18 className,
19}: CourseCardProps) {
20 const cardClass = [
21 styles.card,
22 isFeatured && styles.cardFeatured,
23 variant === 'compact' && styles.cardCompact,
24 variant === 'minimal' && styles.cardMinimal,
25 className,
26 ].filter(Boolean).join(' ');
27
28 return (
29 <article className={cardClass}>
30 <div className={styles.thumbnail}>
31 <img
32 src={thumbnailUrl}
33 alt="" {/* Decorative — title provides the name */}
34 width={320}
35 height={180}
36 loading="lazy"
37 />
38 </div>
39 <div className={styles.body}>
40 <Badge difficulty={difficulty} />
41 <h3 className={styles.title}>{title}</h3>
42 <p className={styles.meta}>
43 {lessonCount} · {duration}
44 </p>
45 <Button
46 className={styles.cta}
47 variant={isEnrolled ? 'secondary' : 'primary'}
48 onClick={onEnroll}
49 aria-label={isEnrolled ? `Continue ${title}` : `Enroll in ${title}`}
50 >
51 {isEnrolled ? 'Continue' : 'Enroll Free'}
52 </Button>
53 </div>
54 </article>
55 );
56}Design QA final checklist:
The complete CourseCard component built from start to finish following the 5-step workflow — from Figma layer reading to production-ready React with CSS Modules.
1/*
2COMPLETE FIGMA-TO-REACT TRANSLATION
3
4Figma Spec (from 5-minute extraction workflow):
5 Component: CourseCard
6 Variants: variant (default, featured), size (standard, compact)
7 Properties: title (text), meta (text), difficulty (text), isFeatured (boolean)
8
9 Outer frame:
10 - Auto Layout: Vertical
11 - Width: Fill (min 260px), Height: Hug
12 - Padding: 0 (thumbnail bleeds to edges)
13 - Gap: 0
14 - Border: 1px #E5E7EB (1.5px + brand border on featured)
15 - Border-radius: 12px
16 - Background: white
17 - Shadow: 0 1px 3px #0000000A, 0 1px 2px #0000000D
18 - Overflow: clip
19
20 Thumbnail (16:9 ratio):
21 - Frame, fixed aspect ratio
22 - Image fill, cover
23
24 Content frame:
25 - Auto Layout: Vertical
26 - Padding: 20px all sides
27 - Gap: 12px
28
29 Difficulty badge (Component instance):
30 - Beginner: green, Intermediate: amber, Advanced: red
31
32 Title:
33 - Inter 700 17px, #0F172A
34 - Line height: 23px = 1.353
35 - 2-line clamp
36
37 Meta:
38 - Inter 400 13px, #6B7280
39
40 CTA Button (Component instance):
41 - Width: Fill container
42 - Height: Fixed 40px
43 - Variant: Primary (not enrolled) / Secondary (enrolled)
44*/
45
46/* CourseCard.module.css */
47.card {
48 display: flex;
49 flex-direction: column;
50 min-width: 16.25rem; /* 260px min-width from spec */
51 border: 1px solid #e5e7eb;
52 border-radius: 0.75rem; /* 12px */
53 box-shadow:
54 0 1px 3px rgb(0 0 0 / 0.04),
55 0 1px 2px rgb(0 0 0 / 0.05);
56 background: white;
57 overflow: hidden;
58 transition: border-color 200ms ease, box-shadow 200ms ease;
59}
60.card:hover {
61 border-color: hsl(220 90% 56% / 0.3);
62 box-shadow: 0 4px 12px rgb(0 0 0 / 0.08);
63}
64.card.featured {
65 border-color: #2563eb;
66 border-width: 1.5px;
67 box-shadow: 0 4px 16px hsl(220 90% 56% / 0.1);
68}
69.thumbnail {
70 aspect-ratio: 16 / 9;
71 overflow: hidden;
72 flex-shrink: 0;
73 background: #eff6ff;
74}
75.thumbnail img { width: 100%; height: 100%; object-fit: cover; }
76.body {
77 display: flex;
78 flex-direction: column;
79 padding: 1.25rem; /* 20px */
80 gap: 0.75rem; /* 12px */
81 flex: 1;
82}
83.badge {
84 display: inline-flex;
85 align-self: flex-start;
86 padding: 0.1875rem 0.5rem;
87 border-radius: 9999px;
88 font-size: 0.6875rem;
89 font-weight: 700;
90 text-transform: uppercase;
91 letter-spacing: 0.08em;
92}
93.beginner { background: #dcfce7; color: #166534; }
94.intermediate { background: #fef3c7; color: #92400e; }
95.advanced { background: #fee2e2; color: #991b1b; }
96.title {
97 font-size: 1.0625rem; /* 17px */
98 font-weight: 700;
99 line-height: 1.353; /* 23/17 */
100 color: #0f172a;
101 margin: 0;
102 display: -webkit-box;
103 -webkit-line-clamp: 2;
104 -webkit-box-orient: vertical;
105 overflow: hidden;
106}
107.meta {
108 font-size: 0.8125rem; /* 13px */
109 color: #6b7280;
110 margin: 0;
111}
112.cta {
113 display: flex;
114 align-items: center;
115 justify-content: center;
116 width: 100%;
117 height: 2.5rem; /* 40px */
118 padding: 0 1rem;
119 border-radius: 0.5rem;
120 font-size: 0.9375rem;
121 font-weight: 600;
122 font-family: inherit;
123 cursor: pointer;
124 border: 1.5px solid transparent;
125 margin-top: auto;
126 transition: 150ms ease;
127}
128.ctaPrimary { background: #2563eb; color: white; border-color: #2563eb; }
129.ctaSecondary { background: white; color: #2563eb; border-color: #2563eb; }
130.ctaPrimary:hover { background: #1d4ed8; border-color: #1d4ed8; }
131.ctaSecondary:hover { background: #eff6ff; }
132.cta:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }
133
134/* RENDERED HTML (what React outputs) */This CourseCard implementation follows the complete 5-step workflow. Step 1 (layer reading) revealed: outer frame clips, thumbnail bleeds to edges, content has 20px padding and 12px gap. Step 2 (component planning) identified: Badge and Button as atom dependencies, a single CourseCard organism. Step 3 (prop derivation) produced: title, meta, difficulty as text props; isFeatured and isEnrolled as booleans. Step 4 (CSS) traces every value to a Figma Inspect reading — 17px font-size becomes 1.0625rem, 23px line-height on 17px font becomes 1.353 unitless, 2-line clamp from the text overflow annotation. Step 5 (JSX) maps each CSS class to its corresponding Figma layer. The design QA checklist runs against this rendered output before the PR is marked ready for review.
This completes the Figma for Developers module. You now have the complete workflow — from navigating a Figma file and reading specs, through building an automated token pipeline, to translating any design to production React components. The next module covers Design Principles for Developers — the visual theory behind the CSS decisions you have been making.
Your progress is saved in this browser, on this device only.