Figma 到 React 自主工作流
概述
此技能编排了将 Figma 设计转换为生产就绪的 React 应用的完整流水线。它从 Figma 中提取设计系统(颜色、排版、间距、效果),将其转换为 Tailwind CSS 配置和 CSS 自定义属性,然后生成功能完善的 TypeScript React 组件,以忠实再现原始设计。
工作流分为四个阶段:交互式发现、TDD 脚手架搭建、自主执行和验证交付。最终产物是一套 React 组件、页面以及设计令牌系统,可直接集成到 Next.js、Vite 或 Remix 项目中,并能达到与 Figma 源文件像素级一致的精度。
核心原则:
- TDD 是强制的 — 先写测试,后写组件,绝不颠倒顺序
- 每个组件都是一个功能完整的 TypeScript 组件,并具有明确的 props 接口
- 所有样式都使用基于 Figma 设计令牌的 Tailwind 实用类
- 没有硬编码的颜色值、字体大小或间距——一切都引用令牌系统
- 图片使用正确的导入或公共目录引用,绝无断链路径
- 输出结果能感知框架(Next.js App Router、Vite SPA、Remix 路由)
- 像素级完美验证 — 使用 pixelmatch 自动进行差异循环比对,而非手动比较
- 应用类型感知 — Chrome 扩展、PWA 和 Web 应用各自拥有合适的测试策略
何时使用
在以下场景使用此技能:
- 将 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
→ 如果存在:跳过所有发现性问题。直接加载规范。
→ 记录日志:“发现 build-spec.json 来自 figma-intake — 跳过发现阶段”
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="英雄区插图" width={1200} height={600} priority />
// Vite - 使用标准导入
import heroImage from "@/assets/images/hero.webp";
<img src={heroImage} alt="英雄区插图" width={1200} height={600} loading="eager" />
规则:
- 以 1x 和 2x 分辨率导出 Figma 图片以支持视网膜屏
- 使用 WebP 格式,并准备 JPEG/PNG 作为备选
- 放置在
public/images/或src/assets/images/目录中 - 每个
<img>必须包含有意义的alt属性 - 首屏/折叠线上方的图片使用
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
生成器使用 ts-morph AST 解析每个组件,并发出 CSF3 故事,包含以下内容:prop 感知的控制、每个字符串字面量联合值的一个变体故事、布尔值的 True/False 故事、on* 回调的动作参数,以及一个 MDX 自动文档页面。行为由 .claude/pipeline.conf 配置。
...(已截断以进行翻译)