Figma 到 React 自主工作流程
概述
此技能負責協調將 Figma 設計轉換為生產就緒 React 應用程式的完整流程。它從 Figma 中提取設計系統(顏色、排版、間距、效果),將其轉換為 Tailwind CSS 設定與 CSS 自訂屬性,然後生成能忠實重現原始設計的功能性 TypeScript React 元件。
工作流程分為四個階段:互動探索、TDD 支架、自主執行與驗證交付。最終產出是一組 React 元件、頁面以及一個設計代號系統,可直接整合到 Next.js、Vite 或 Remix 專案中,並經過驗證,可達到像素等級的準確度。
核心原則:
- TDD 是強制的 — 測試必須在元件之前撰寫,絕對不能在之後
- 每個元件都是一個功能性 TypeScript 元件,並帶有明確的 prop 介面
- 所有樣式都使用由來自 Figma 的設計代號支援的 Tailwind 工具類別
- 不允許硬編碼的顏色值、字型大小或間距 — 所有內容都參考代號系統
- 圖片使用正確的導入或公開目錄引用,絕不使用中斷的路徑
- 輸出內容會感知框架(Next.js App Router、Vite SPA、Remix 路由)
- 像素完美驗證 — 使用 pixelmatch 進行自動化差異比對循環,而非手動比較
- 應用程式類型感知 — Chrome 擴充功能、PWA 和網頁應用程式各自獲得適當的測試策略
使用時機
在以下情況使用此技能:
- 將 Figma 設計檔轉換為 React 應用程式
- 從 Figma 中提取設計系統並轉換為 Tailwind 設定
- 從 Figma 元件集中生成元件庫
- 根據 Figma 重新設計重建現有 UI
- 從 Figma 原型開始新的 React 專案
觸發短語:
- "將這個 Figma 設計轉換為 React"
- "從 Figma 生成 React 元件"
- "從 Figma 提取設計代號"
- "從這個 Figma 檔案建立 React 應用程式"
- "Figma 到 Next.js"
- "Figma 到 Vite"
階段 1:探索(互動式)
此階段需要使用者輸入。在使用者確認計畫之前,請勿自主進行。
流程整合檢查
在詢問任何問題之前,檢查上游工件是否存在:
1. 檢查:.claude/plans/build-spec.json
→ 如果存在:跳過所有探索問題。直接載入規格。
→ 記錄:"找到來自 figma-intake 的 build-spec.json — 跳過探索"
2. 檢查:src/styles/design-tokens.lock.json
→ 如果存在:在階段 2 中跳過代號提取。使用鎖定檔中的值。
→ 記錄:"找到 design-tokens.lock.json — 使用鎖定的代號值"
如果 build-spec.json 存在,則直接跳至階段 2,並預先載入規格中的框架、元件和選項。
步驟 1.1:收集 Figma 上下文
使用 Figma MCP 檢查設計檔。
1. get_metadata — 檔案名稱、頁面、最後修改時間、元件數量
2. get_variable_defs — 設計代號(顏色、排版、間距、效果)
3. get_design_context — 版面結構、元件層級、自動佈局設定
4. get_screenshot — 每個頁面/框架的視覺參考
步驟 1.2:提取設計代號
將 Figma 變數和樣式對應到代號結構:
設計代號
├── 顏色
│ ├── 原始色(blue-500、gray-100 等)
│ ├── 語意色(primary、secondary、destructive、muted)
│ └── 元件專用(card-bg、button-primary、input-border)
├── 排版
│ ├── 字型家族(包含後備字型堆疊)
│ ├── 字型大小(比例:xs 到 5xl)
│ ├── 字型粗細
│ ├── 行高
│ └── 字母間距
├── 間距
│ ├── 基本單位
│ └── 比例(0.5 到 96)
├── 邊框圓角
│ ├── 比例(sm、md、lg、xl、full)
│ └── 元件專用覆寫
├── 陰影/效果
│ ├── 盒子陰影(sm、md、lg、xl)
│ └── 下拉陰影
└── 中斷點
└── 手機、平板、桌上型電腦、寬螢幕
步驟 1.3:調查元件
識別所有 Figma 元件並將其對應到 React 元件:
| Figma 元件 | React 元件 | 類別 |
|---|---|---|
| Button/Primary | <Button variant="primary"> | 基本元件 |
| Card | <Card> | 容器 |
| Navigation/Header | <Header> | 佈局 |
| Hero Section | <HeroSection> | 區段 |
| Input/Text | <Input type="text"> | 表單 |
步驟 1.4:確認輸出目標
要求使用者確認:
- 框架: Next.js(App Router)、Next.js(Pages Router)、Vite 或 Remix
- 樣式方法: Tailwind CSS(預設)、CSS Modules 或 styled-components
- 元件庫基礎: shadcn/ui、Radix UI、Headless UI 或無
- 狀態管理: React state、Zustand、Jotai 或無
- 輸出目錄: 例如
src/components/、app/等
在繼續之前,呈現元件清單和代號摘要以供使用者批准。
階段 2:執行(自主)
一旦使用者批准探索計畫,就自主執行所有步驟。
代號鎖定檔限制
如果 src/styles/design-tokens.lock.json 存在,所有生成的程式碼都必須參考鎖定檔中的值:
- 顏色:使用鎖定檔中對應的 Tailwind 類別,絕不近似十六進位值
- 排版:使用鎖定檔中的確切字型家族、大小和粗細
- 間距:使用鎖定檔中的間距比例值
- 文字內容:使用
lockfile.textContent中的確切字串
當鎖定檔存在時,絕不近似或猜測值。 如果鎖定檔中沒有某個值,請先將其新增到鎖定檔中,然後再引用它。
測試優先閘門(強制)
強制執行 TDD。 在測試檔存在之前,不得開始實作元件。這是一個硬性閘門,而非建議。
在撰寫任何元件實作之前:
1. 執行:./scripts/verify-test-coverage.sh
→ 如果退出碼為 1:停止。先叫用 tdd-from-figma 技能。
→ 如果退出碼為 0:繼續進行實作。
2. 對於要生成的每個元件:
a. 要求:src/components/**/{ComponentName}.test.tsx 存在
→ 如果缺少:停止。先撰寫測試(叫用 tdd-from-figma)。
→ 這不是可選的。沒有預先存在的測試,任何元件都不能出貨。
b. 讀取測試檔以了解預期行為
c. 實作必須讓所有測試通過
d. 在撰寫元件後執行:pnpm vitest run {test-file}
e. 如果測試失敗:修正元件,而不是測試
3. 在批次中的所有元件撰寫完成後:
→ 執行:pnpm vitest run --reporter=verbose
→ 所有測試都必須通過(TDD 的綠色階段)才能繼續
→ 如果有任何失敗:修正元件直到綠色。不要繼續進行階段 3。
4. 驗證測試覆蓋率達到門檻:
→ 執行:pnpm vitest run --coverage
→ 必須達到:pipeline.config.json → tdd.coverageThreshold(預設:80%)
為什麼要有這個閘門: 測試編碼了鎖定檔中的確切值(文字內容、ARIA 角色、元件結構)。如果沒有預先撰寫的測試,就沒有機制來驗證實作是否符合設計。測試是 Figma 設計與 React 程式碼之間的合約。
步驟 2.1:生成 Tailwind 設定
使用提取的代號建立或擴充 tailwind.config.ts:
// tailwind.config.ts
import type { Config } from "tailwindcss";
const config: Config = {
content: ["./src/**/*.{ts,tsx}", "./app/**/*.{ts,tsx}"],
theme: {
extend: {
colors: {
// 從 Figma 變數對應的語意代號
primary: {
DEFAULT: "var(--color-primary)",
foreground: "var(--color-primary-foreground)",
// ... 完整比例
},
// ... 所有顏色代號
},
fontFamily: {
sans: ["var(--font-sans)", "system-ui", "sans-serif"],
heading: ["var(--font-heading)", "system-ui", "sans-serif"],
},
fontSize: {
// 從 Figma 排版樣式對應
},
spacing: {
// 從 Figma 間距變數對應
},
borderRadius: {
// 從 Figma 角落圓角代號對應
},
boxShadow: {
// 從 Figma 效果樣式對應
},
},
},
plugins: [],
};
export default config;
使用自訂屬性生成一個伴隨的 CSS 檔:
/* src/styles/tokens.css */
:root {
/* 顏色 - 原始色 */
--color-blue-500: #3b82f6;
/* ... */
/* 顏色 - 語意 */
--color-primary: var(--color-blue-500);
--color-primary-foreground: #ffffff;
/* ... */
/* 排版 */
--font-sans: "Inter", system-ui, sans-serif;
--font-heading: "Plus Jakarta Sans", system-ui, sans-serif;
/* ... */
}
步驟 2.2:生成 React 元件
對於在階段 1 中識別的每個元件,生成:
每個元件的檔案結構:
src/components/
├── ui/
│ ├── button.tsx # 基本元件
│ ├── input.tsx
│ ├── card.tsx
│ └── ...
├── layout/
│ ├── header.tsx # 佈局元件
│ ├── footer.tsx
│ └── ...
├── sections/
│ ├── hero-section.tsx # 頁面區段
│ ├── features-grid.tsx
│ └── ...
└── pages/ # 完整頁面組合(或 Next.js 的 app/)
├── home-page.tsx
└── ...
元件範本:
// src/components/ui/button.tsx
import { type ButtonHTMLAttributes, forwardRef } from "react";
import { cn } from "@/lib/utils";
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: "primary" | "secondary" | "outline" | "ghost";
size?: "sm" | "md" | "lg";
}
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant = "primary", size = "md", ...props }, ref) => {
return (
<button
ref={ref}
className={cn(
"inline-flex items-center justify-center rounded-md font-medium transition-colors",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary",
"disabled:pointer-events-none disabled:opacity-50",
{
"bg-primary text-primary-foreground hover:bg-primary/90": variant === "primary",
"bg-secondary text-secondary-foreground hover:bg-secondary/90": variant === "secondary",
"border border-input bg-transparent hover:bg-accent": variant === "outline",
"hover:bg-accent hover:text-accent-foreground": variant === "ghost",
},
{
"h-8 px-3 text-sm": size === "sm",
"h-10 px-4 text-sm": size === "md",
"h-12 px-6 text-base": size === "lg",
},
className
)}
{...props}
/>
);
}
);
Button.displayName = "Button";
export { Button, type ButtonProps };
元件生成規則:
- 所有元件都是功能性的,並在適當的情況下使用
forwardRef - 每個元件都有明確的 TypeScript
interface用於 props - 所有樣式都使用引用代號值的 Tailwind 類別
- 元件接受
classNameprop 以便組合 - 使用
cn()工具函式(clsx + tailwind-merge)進行類別合併 - 為互動元素包含適當的 ARIA 屬性
- 同時匯出元件及其 props 類型
步驟 2.3:生成頁面組合
將區段元件組合成符合 Figma 框架的完整頁面:
// app/page.tsx (Next.js App Router)
import { Header } from "@/components/layout/header";
import { HeroSection } from "@/components/sections/hero-section";
import { FeaturesGrid } from "@/components/sections/features-grid";
import { Footer } from "@/components/layout/footer";
export default function HomePage() {
return (
<div className="flex min-h-screen flex-col">
<Header />
<main className="flex-1">
<HeroSection />
<FeaturesGrid />
</main>
<Footer />
</div>
);
}
步驟 2.4:處理圖片和資產
對於來自 Figma 的靜態圖片:
// Next.js - 使用 next/image
import Image from "next/image";
import heroImage from "@/assets/images/hero.webp";
<Image src={heroImage} alt="Hero 插圖" width={1200} height={600} priority />
// Vite - 使用標準導入
import heroImage from "@/assets/images/hero.webp";
<img src={heroImage} alt="Hero 插圖" width={1200} height={600} loading="eager" />
規則:
- 從 Figma 以 1x 和 2x 匯出圖片以支援 Retina 螢幕
- 使用 WebP 格式,並提供 JPEG/PNG 作為備援
- 放置在
public/images/或src/assets/images/中 - 每個
<img>都必須有有意義的alt屬性 - Hero / 上方折頁圖片使用
priority(Next.js)或loading="eager" - 下方折頁圖片使用
loading="lazy"
步驟 2.5:生成工具檔
src/lib/
├── utils.ts # cn() 輔助函式、格式化工具
└── fonts.ts # 字型載入(Next.js:next/font,Vite:@fontsource)
步驟 2.6:執行測試(如果測試檔存在)
生成所有元件後,如果 tdd-from-figma 已撰寫測試檔:
pnpm vitest run --reporter=verbose
如果測試失敗: 修正元件實作以符合測試預期。測試是權威性的——它們編碼了鎖定檔中的確切值。請勿修改測試以符合實作。
迭代直到所有測試通過(TDD 的綠色階段)。
步驟 2.7:生成 Storybook 故事(非阻塞)
一旦元件建置完成且其單元測試通過,便自動生成 Storybook 故事和 MDX 文件。呼叫 storybook-story-generation 技能(或直接執行指令稿)。這是流程的 階段 4.5,且是 非阻塞 的——此處的失敗會記錄警告,流程會繼續執行。
# 為每個元件生成 .stories.tsx + .mdx(跳過已有故事的元件)
./scripts/generate-stories.sh
# 在不寫入的情況下預覽,或強制重新生成
./scripts/generate-stories.sh --dry-run
./scripts/generate-stories.sh --force
階段 3:驗證與測試
元件層級 TDD 驗證
在停止所有生成活動後,對整個專案執行最終驗證循環:
# 1. 執行完整的測試套件
pnpm vitest run --reporter=verbose
# 2. 檢查型別
pnpm tsc --noEmit
# 3. 檢查覆蓋率
pnpm vitest run --coverage
所有測試必須通過。 如果測試失敗,請修正元件實作,而不是測試。測試編碼了鎖定檔中的確切值、結構和行為契約。
像素完美驗證
生成 PixelMatch 指令稿以自動化視覺回歸測試:
# ./scripts/pixel-diff.sh
# 1. 建置應用程式
# 2. 為每個頁面/元件擷取螢幕截圖
# 3. 與 Figma 基準進行像素差異比對
# 4. 報告通過/失敗與差異百分比
E2E 測試
為 Playwright 或 Cypress 生成端到端測試(根據專案類型和慣例):
// e2e/home-page.spec.ts
import { test, expect } from "@playwright/test";
test.describe("首頁", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/");
});
test("顯示標題", async ({ page }) => {
await expect(page.locator("h1")).toContainText("歡迎");
});
test("導航連結存在", async ({ page }) => {
await expect(page.locator("nav a")).toHaveCount(4);
});
});
組態
可選的 pipeline.config.json 控制閘門、階段和行為:
{
"tdd": {
"enforce": true,
"coverageThreshold": 80,
"failOnMissingTests": true
},
"pixelDiff": {
"enabled": true,
"threshold": 0.02,
"generateBaseline": false
},
"e2e": {
"framework": "playwright",
"viewport": {
"mobile": [375, 667],
"tablet": [768, 1024],
"desktop": [1280, 800]
}
},
"output": {
"framework": "next-app",
"componentDir": "src/components",
"pageDir": "app",
"styling": "tailwind"
},
"storybook": {
"enabled": true,
"autoGenerate": true
},
"appType": "spa",
"chromeExtension": false,
"pwa": false
}
輸出
此工作流程的輸出包含:
- 設計代號系統 在
tailwind.config.ts和src/styles/tokens.css中 - React 元件 在
src/components/中(基本元件、佈局、區段) - 頁面組合 在
app/或src/pages/中 - 通過的測試 用於單元測試、型別檢查、像素差異比對和 E2E
- Storybook 故事 用於開發(透過非阻塞生成)
每個工作流程的結果都是完全可重現的——相同的 Figma 檔案和設定會產生完全相同的輸出。