figma-lens: Figma に一致する UI を構築
figma-lens は Figma プラグインとローカルブリッジを組み合わせたツールです。ユーザーが Figma デスクトップでフレームを選択し、Export to Claude をクリックすると、ブリッジがフレームごとに 1 つのフォルダを書き出します。
design/
INDEX.md ← エクスポートされた全フレームの一覧(ここから開始)
<frame>/
spec.md ← 最初に読むこと: 正確な値を持つコンパクトなレイヤーツリー
spec.json ← spec.md だけでは不十分な場合の完全なデータ(+ Figma からのレイヤーごとの CSS)
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 リポジトリの CLI です(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を 1 枚ずつ詳細に確認します。[email protected]は全体像を把握するためだけに使用します。大きな画像は表示時に縮小されるためです。値は常にスペックから取得し、ピクセルを測定することはしません。 - コンポーネントマップを使用する(「コンポーネントマップ」を参照)。spec.md 内の
⇒ USE …は必須です: そのウィジェット/コンポーネントとそのプロップマッピングを使用し、生の値の代わりにマップされたトークン((color/primary ⇒ AppColors.primary))を使用します。(not in map)とマークされた項目については、コードベース内で既存のテーマとウィジェット(Flutter ではThemeData、AppColors、TextTheme、間隔定数、lib/widgets、Web では CSS 変数、Tailwind 設定、src/components)を検索します。トークンが存在しない場合のみ生の値を使用し、どのトークンを追加したかをユーザーに伝えます。 - コンポーネントを再利用する。
component Button {Type=Primary}とマークされたレイヤーは、プロジェクトの既存の Button ウィジェットまたはコンポーネントを一致するプロップで使用する必要があります。存在しない場合のみ新しいものを作成し、再利用可能にしてマップに追加します。 - トップダウンでツリーに従って構築する。 オートレイアウトは Row/Column/flex にマッピングされます(以下の表を参照)。
@x,yレイヤー(オートレイアウトがない、またはabsolute)のみ Stack/Positioned または絶対配置が必要です。 - エクスポートされたファイルを使用する。
assets/*.svgとimages/*をプロジェクトのアセットフォルダに配置し、参照します。アイコンを再描画したり、別のアイコンセットに置き換えたりしないでください。 - テキストは spec.md の
"…"から正確にコピーする。 大文字小文字、句読点、アラビア語/RTL 文字列を含みます。複数のrun行があるテキストはリッチテキスト(TextSpan / ネストされたスパン)が必要です。 - 比較と修正のループ(下記)で検証する。コードを読んだだけで画面が完成したと宣言せず、スクリーンショットの差分で証明してください。
比較と修正のループ
画面を構築した後、実際の結果を Figma と比較し、異なる部分を修正します:
- ビルドを実行してその画面を表示します:
- Web: 開発サーバーを起動し、ルートを開きます。
- Flutter web:
flutter run -d web-server --web-port 8080を実行し、その URL を使用します。 - Flutter Android: エミュレーター/デバイスでアプリを実行し、画面に移動します。
- チェックする:
- Web / Flutter web:
figma-lens check --design design/<frame> --url http://localhost:<port>/<route>(ビューポートはフレームのサイズとスケールに自動的に設定されます)。 - Android:
figma-lens check --design design/<frame> --adb --crop-top <ステータスバー px> [--crop-bottom <ナビゲーションバー px>](ステータスバー ≈ 24–48 dp × デバイス密度; アプリコンテンツのみが比較されるように切り取ります)。
- Web / Flutter web:
design/<frame>/compare/report.mdを読む(不一致率、最大の差異領域、および各領域の Figma レイヤー ID)そしてcompare/side-by-side.pngを見る(Figma | ビルド | 差分、赤 = 異なる部分)。- トップダウンで修正する: 最初に高さまたは間隔の違い(それ以降のすべてをずらす)、次にサイズ、色、タイポグラフィ、半径、シャドウ。リストされた各レイヤー ID を spec.md で調べて正確な値を確認します。
- チェックを再実行する。 レポートには前回のスコアが表示されるため、進捗状況を確認できます。
- 不一致が 3% 以下になったら停止する(テキストのアンチエイリアシングは 0 にはなりません)、または連続する 2 回の実行で改善が見られなくなったら停止します。その後、最終スコアと一致させられなかった項目、およびその理由(フォントの欠落、プラットフォームのレンダリング、動的データ)を報告します。
ルール: 実際のコードを修正し、差分を不正に操作しないこと(アプリ内にデザインのスクリーンショットや画像を入れない)。チェック時にはデザインと同じデータ/テキストを使用します(必要に応じてモックデータ)。そうしないと、テキストの違いがスコアを支配します。
コンポーネントマップ
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矢印が表示されるようにします。ブリッジは新しいエクスポートごとにマップも適用します。 - マップされていないコンポーネントに対して新しい再利用可能なウィジェットを作成した場合は、マップに追加します。
spec.md の読み方
- 単位はデザインピクセル(design px)で、CSS px および Flutter の論理ピクセル(logical px)と同じです。スクリーンショットのスケールを掛けないでください。
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 = left/top、MAX = right/bottom、STRETCH = 両方、CENTER、SCALE)。bg A + Bはペイントを下から上にリストします。(name)は Figma 変数、[style name]は Figma スタイルです。- 色は
#RRGGBBまたは#RRGGBBAA(アルファは最後)です。 - テキストは
"chars" · Family Style (weight) size/lineHeight colorの形式を使用します。lineHeight は px、%、または auto です。lsは文字間隔(px)、またはフォントサイズの % です。 radius 12はすべての角を設定します。radius tl,tr,br,blは各角を設定します。smoothingは iOS スタイルの角のスムージングです。近似するか、Flutter でスクアークルパッケージが使用されていない限り無視します。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 | mainAxisSize: MainAxisSize.min |
| 親の主軸上の FILL | Expanded(または Flexible); grow N は flex: N を設定 |
| 親の交差軸上の FILL | 親で CrossAxisAlignment.stretch、または SizedBox(width: double.infinity) |
| FIXED w×h | SizedBox(width: w, height: h) |
@x,y / absolute | Stack + Positioned(left: x, top: y); 右/下には pin を使用 |
| bg 単色 | BoxDecoration(color: Color(0xAARRGGBB))。アルファを先頭に移動: #11223380 → 0x80112233 |
| グラデーション線形 from/to | LinearGradient(begin: Alignment(fx*2-1, fy*2-1), end: Alignment(tx*2-1, ty*2-1), colors, stops) を使用。from/to は spec.json から |
| radius | BorderRadius.circular(r) または BorderRadius.only(...) |
| 境界線 W 色 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(またはペイントの色アルファ) |
| テキスト size/lineHeight | TextStyle(fontSize: size, height: lineHeight / size)。% の場合: height: pct/100。Figma の垂直方向の中央揃えに合わせるには leadingDistribution: TextLeadingDistribution.even を設定。 |
| ウェイト 600 | FontWeight.w600 |
| ls px / ls % | letterSpacing: px / letterSpacing: size * pct / 100 |
| 省略記号 maxLines N | maxLines: N, overflow: TextOverflow.ellipsis |
| align CENTER | textAlign: TextAlign.center。RTL 画面の場合は TextAlign.start と EdgeInsetsDirectional を使用。 |
| assets/x.svg | SvgPicture.asset('assets/…/x.svg', width:, height:)(flutter_svg) |
| 画像フィル FILL / FIT | Image.asset(fit: BoxFit.cover / BoxFit.contain) |
Web(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 を使用) |
色 #RRGGBBAA | そのまま有効な CSS |
グラデーション線形 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) |
| テキスト | font: weight size/lineHeight family; letter-spacing(ls % → pct/100 em) |
| 省略記号 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 値をクロスチェックするために使用します。トークンとレイアウト構造は引き続き上記のツリーから取得します。
ルール
- スペックの値は画像で見えるものより優先されます。矛盾する場合はスペックを信頼し、矛盾点を指摘してください。
- ユーザーがトークンへの正規化を依頼しない限り、値を丸めたり「改善」したりしないでください(14 は 14 のまま、16 にしない)。
- 非表示のレイヤーはエクスポートされません。エクスポートに含まれていない状態(ホバー、エラー、空)をでっち上げないでください。それらのフレームを依頼してください。
- 欠落しているフォントを報告してください。フォントファミリーがプロジェクトにバンドルされていない場合は、その旨を伝え、ユーザーに何を追加すべきか(pubspec フォント / @font-face)を指示します。
- 複数のブレークポイントや状態がある場合は、ユーザーに各フレームをエクスポートするよう依頼してください。それぞれが独自のフォルダになります。