figma-lens:建立與 Figma 相符的 UI
figma-lens 是一個 Figma 外掛程式加上本地端橋接器。使用者在 Figma 桌面版中選取框架,然後點擊 Export to Claude。橋接器會為每個框架寫入一個資料夾:
design/
INDEX.md ← 所有已匯出框架的清單(從這裡開始)
<frame>/
spec.md ← 優先閱讀:精簡的圖層樹,包含精確數值
spec.json ← 完整資料(+ Figma 為每個圖層產生的 CSS),當 spec.md 不夠用時使用
tokens.json ← 變數(所有模式)、樣式、原始數值的使用次數
[email protected] ← 完整渲染(預設 2x)
sections/NN-name.png ← 頂層區塊的全解析度裁切
assets/*.svg ← 圖示/向量圖,可直接使用
images/* ← 用作影像填充的原始點陣圖
compare/ ← 由 `figma-lens check` 寫入(report.md、side-by-side.png、diff.png)
figma-lens.map.json ← (專案根目錄)Figma 元件/標記 → 此專案中的實際程式碼
figma-lens 指令是來自 figma-lens 儲存庫的命令列工具(在該目錄中執行 npm link 可使其成為全域指令;否則請執行 node <figma-lens>/bin/figma-lens.js …)。執行 figma-lens help 可查看所有指令。
如果尚未匯出
- 為使用者的專案啟動橋接器並讓它持續在背景執行:
figma-lens bridge --out <project>/design。 - 請使用者在 Figma 桌面版中選取框架 → 外掛程式 → 開發 → Figma Lens → Export to Claude。
- 讀取
design/INDEX.md以確認匯出成功。
切勿在可以匯出設計稿時,僅根據描述猜測設計。請要求使用者進行匯出。
工作流程
- 完整閱讀
spec.md(檔案很小)。記下框架尺寸、佈局樹、變數和樣式,以及資產。 - 查看圖片。 逐一閱讀
sections/*.png以取得細節。僅使用[email protected]來查看整體畫面,因為大型圖片在檢視時會被縮小。所有數值都來自 spec,切勿從像素測量中取得。 - 使用元件對應表(請參閱下方「元件對應表」)。spec.md 中每個
⇒ USE …都是強制性的:必須使用該 widget/元件及其屬性對應,並使用對應的標記((color/primary ⇒ AppColors.primary))而非原始數值。對於標記為(not in map)的項目,請在程式碼中搜尋既有主題和 widget(Flutter 的ThemeData、AppColors、TextTheme、間距常數、lib/widgets;網頁的 CSS 變數、Tailwind 設定、src/components)。僅在找不到任何標記時使用原始數值,並告知使用者您新增了哪些。 - 重複使用元件。 標記為
component Button {Type=Primary}的圖層應使用專案中已有的 Button widget 或具有相符屬性的元件。僅在找不到現有元件時才建立新的可重複使用元件,並將其新增至對應表。 - 從上到下建構,遵循樹狀結構。 自動佈局對應到 Row/Column/flex(對應表如下)。僅
@x,y圖層(無自動佈局,或標記為absolute)才需要使用 Stack/Positioned 或絕對定位。 - 使用匯出的檔案。 將
assets/*.svg和images/*放入專案的資產資料夾中並加以引用。切勿重新繪製圖示,也切勿替換成不同的圖示集。 - 完全複製文字,包含 spec.md 中
"…"內的大小寫、標點符號和阿拉伯文/由右至左字串。具有多個run行的文字需要 rich text(TextSpan / 巢狀 spans)。 - 使用比對與修正循環進行驗證(如下)。不要只憑閱讀程式碼就宣告畫面完成;必須透過截圖差異來證明。
比對與修正循環
建立畫面後,比對實際結果與 Figma 設計稿,並修正差異:
- 執行建置以顯示該畫面:
- 網頁:啟動開發伺服器並開啟該路由。
- Flutter web:
flutter run -d web-server --web-port 8080,然後使用該 URL。 - Flutter Android:在模擬器/裝置上執行應用程式,並導航到該畫面。
- 進行檢查:
- 網頁 / Flutter web:
figma-lens check --design design/<frame> --url http://localhost:<port>/<route>(檢視區會自動設定為框架的尺寸和縮放比例)。 - Android:
figma-lens check --design design/<frame> --adb --crop-top <狀態列像素高度> [--crop-bottom <導覽列像素高度>](狀態列約 24–48 dp × 裝置密度;將其裁切掉,僅比對應用程式內容)。
- 網頁 / Flutter web:
- 閱讀
design/<frame>/compare/report.md(不符百分比、差異最大的區域以及每個區域中的 Figma 圖層 ID)並查看compare/side-by-side.png(Figma | 建置結果 | 差異,紅色 = 不同)。 - 從上到下修正:先修正高度或間距差異(它們會影響下方所有內容),然後依序修正尺寸、顏色、字體排版、圓角半徑、陰影。在 spec.md 中查詢每個列出的圖層 ID 以取得精確數值。
- 重新執行檢查。 報告會顯示先前的分數,因此您可以查看進度。
- 在以下情況停止:不符比例 ≤ 3%(文字反鋸齒永遠不會達到 0),或連續兩次執行都沒有改善。然後回報最終分數以及您無法比對的項目及其原因(缺少字型、平台渲染差異、動態資料)。
規則:修正實際的程式碼,切勿欺騙差異比對(不要在應用程式內部使用設計稿的截圖或圖片)。檢查時請使用與設計稿相同的資料/文字(必要時使用模擬資料),否則文字差異將主導分數。
元件對應表
figma-lens.map.json(專案根目錄)將 Figma 元件和標記對應到此專案的程式碼:
{
"components": {
"Button": { "flutter": "lib/widgets/app_button.dart#AppButton", "web": "src/components/Button.tsx#Button", "props": { "Label": "label", "Type": "variant" } },
"Icon/*": "SvgPicture.asset('assets/icons/<name>.svg')"
},
"tokens": { "color/primary": { "flutter": "AppColors.primary", "web": "var(--color-primary)" } }
}
- 尚無對應表 → 執行
figma-lens map --design design。它會從匯出內容中新增每個元件和標記,並使用空值 ("")。 - 自行填寫:在程式碼庫中搜尋每個元件(依名稱、外觀、屬性)和每個標記值;寫入
path#Symbol(或一個表達式)。僅在尚無任何內容時才保留""。"Prefix/*"可匹配整個系列(例如圖示)。 - 編輯對應表後,執行
figma-lens render design/<frame>,以便 spec.md 顯示⇒ USE箭頭。橋接器也會在每次新匯出時套用該對應表。 - 當您為未對應的元件建立新的可重複使用 widget 時,請將其新增至對應表。
閱讀 spec.md
- 單位為設計像素,等同於 CSS px 和 Flutter 邏輯像素。切勿乘以截圖的縮放比例。
row|column gap G pad t,r,b,l main:X cross:Y是自動佈局。pad 16表示所有四個邊都是 16。gap auto表示 space-between。w:FILL h:HUG表示每個軸的尺寸:FIXED(顯示精確尺寸)、HUG(包裹內容)或 FILL(佔用可用空間)。@x,y是父層級內的位置。absolute表示自動佈局內絕對定位的子層級。pin H/V給出約束條件(MIN = 左/上,MAX = 右/下,STRETCH = 兩者,CENTER,SCALE)。bg A + B從下到上列出填充。(name)是 Figma 變數,[style name]是 Figma 樣式。- 顏色為
#RRGGBB或#RRGGBBAA(Alpha 在後)。 - 文字使用格式
"chars" · Family Style (weight) size/lineHeight color。lineHeight 為 px、% 或 auto。ls是字母間距,單位為 px,或以字型大小的百分比表示。 radius 12設定所有角落。radius tl,tr,br,bl分別設定每個角落。smoothing是 iOS 風格的角落平滑;請近似處理,或在 Flutter 中忽略(除非使用了 squircle 套件)。drop-shadow x,y blur B spread S color、inner-shadow、layer-blur R、background-blur R。→ assets/x.svg表示該圖層是一個圖示。使用指定尺寸的 SVG。vars{itemSpacing=space/md}表示該圖層屬性已繫結到該變數。請使用該標記。
Flutter 對應表
| Figma | Flutter |
|---|---|
| column / row | Column / Row(在 Flutter ≥ 3.27 上使用 spacing: G,否則使用 SizedBox 間距) |
| wrap, rowGap R | Wrap(spacing: G, runSpacing: R) |
| pad t,r,b,l | Padding(padding: EdgeInsets.fromLTRB(l, t, r, b))。注意順序。 |
| main MIN / CENTER / MAX / SPACE_BETWEEN | MainAxisAlignment.start / center / end / spaceBetween |
| cross MIN / CENTER / MAX / BASELINE | CrossAxisAlignment.start / center / end / baseline(baseline 需要 textBaseline) |
| HUG on main axis | mainAxisSize: MainAxisSize.min |
| FILL on parent's main axis | Expanded(或 Flexible);grow N 設定 flex: N |
| FILL on parent's cross axis | 父層級的 CrossAxisAlignment.stretch,或 SizedBox(width: double.infinity) |
| FIXED w×h | SizedBox(width: w, height: h) |
@x,y / absolute | Stack + Positioned(left: x, top: y);使用 pin 設定 right/bottom |
| bg solid | BoxDecoration(color: Color(0xAARRGGBB))。將 Alpha 移到前面:#11223380 → 0x80112233。 |
| gradient-linear from/to | LinearGradient(begin: Alignment(fx*2-1, fy*2-1), end: Alignment(tx*2-1, ty*2-1), colors, stops),使用 spec.json 中的 from/to |
| radius | BorderRadius.circular(r) 或 BorderRadius.only(...) |
| border W color inside | Border.all(width: W, color: …)(inside 是 Flutter 的預設值;center/outside 需要 strokeAlign) |
| drop-shadow x,y blur B spread S | BoxShadow(offset: Offset(x, y), blurRadius: B, spreadRadius: S, color: …) |
| background-blur R | ClipRRect + BackdropFilter(filter: ImageFilter.blur(sigmaX: R/2, sigmaY: R/2)) |
| opacity | Opacity(或顏色的 Alpha,適用於填充) |
| text size/lineHeight | TextStyle(fontSize: size, height: lineHeight / size)。對於 %:height: pct/100。設定 leadingDistribution: TextLeadingDistribution.even 以匹配 Figma 的垂直置中。 |
| weight 600 | FontWeight.w600 |
| ls px / ls % | letterSpacing: px / letterSpacing: size * pct / 100 |
| ellipsis maxLines N | maxLines: N, overflow: TextOverflow.ellipsis |
| align CENTER | textAlign: TextAlign.center。對於由右至左的畫面,使用 TextAlign.start 和 EdgeInsetsDirectional。 |
| assets/x.svg | SvgPicture.asset('assets/…/x.svg', width:, height:)(flutter_svg) |
| image fill FILL / FIT | Image.asset(fit: BoxFit.cover / BoxFit.contain) |
網頁(CSS / Tailwind / React)對應表
| Figma | CSS |
|---|---|
| column / row | display:flex; flex-direction: column / row; gap: Gpx |
| wrap, rowGap R | flex-wrap: wrap; column-gap: G; row-gap: R |
| pad t,r,b,l | padding: t r b l(順序與 CSS 相同) |
| main MIN / CENTER / MAX / SPACE_BETWEEN | justify-content: flex-start / center / flex-end / space-between |
| cross MIN / CENTER / MAX / BASELINE | align-items: flex-start / center / flex-end / baseline |
| HUG | width/height: fit-content(或直接 auto) |
| FILL(主軸) | flex: 1 1 0; min-width: 0 |
| FILL(交錯軸) | align-self: stretch |
| FIXED | width: Wpx; height: Hpx; flex-shrink: 0 |
@x,y / absolute | 父層級 position: relative;子層級 position:absolute; left:x; top:y(使用 pin 設定 right/bottom) |
顏色 #RRGGBBAA | 直接作為有效的 CSS |
gradient-linear Ndeg | linear-gradient(Ndeg, c1 p1%, c2 p2%) |
| drop-shadow | box-shadow: x y B S color(inner-shadow 加上 inset) |
| layer-blur / background-blur R | filter: blur(R/2 px) / backdrop-filter: blur(R/2 px) |
| text | font: weight size/lineHeight family; letter-spacing(ls % → pct/100 em) |
| ellipsis maxLines N | display:-webkit-box; -webkit-line-clamp:N; -webkit-box-orient:vertical; overflow:hidden |
| assets/x.svg | <img src> 或內嵌 SVG;維持匯出的尺寸 |
spec.json 也包含 Figma 自身為大多數圖層產生的 CSS(css)。使用它來交叉檢查 CSS 數值;標記和佈局結構仍來自上方的樹狀結構。
規則
- spec 中的數值優先於您在圖片中看到的內容。當兩者衝突時,請相信 spec 並提及此衝突。
- 請勿四捨五入或「改善」數值(14 維持 14,而非 16),除非使用者要求正規化為標記。
- 隱藏的圖層不會被匯出。請勿發明匯出中不存在的狀態(hover、error、empty);請要求那些框架的匯出。
- 標記缺少的字型。如果字型家族未捆綁在專案中,請告知使用者並告訴他們需要新增什麼(pubspec fonts / @font-face)。
- 對於多個斷點或狀態,請要求使用者匯出每個框架。每個框架都會變成自己的資料夾。