figma-lens : construire une UI qui correspond à Figma
figma-lens est un plugin Figma associé à un pont local. L'utilisateur sélectionne des frames dans Figma desktop et clique sur Exporter vers Claude. Le pont écrit un dossier par frame :
design/
INDEX.md ← liste de tous les frames exportés (commencez ici)
<frame>/
spec.md ← À LIRE EN PREMIER : arbre des couches compact avec valeurs exactes
spec.json ← données complètes (+ CSS par couche issu de Figma) quand spec.md ne suffit pas
tokens.json ← variables (tous les modes), styles, comptes d'utilisation des valeurs brutes
[email protected] ← rendu complet (2x par défaut)
sections/NN-name.png ← vignettes des blocs de haut niveau en pleine résolution
assets/*.svg ← icônes/illustrations vectorielles, prêtes à l'emploi
images/* ← bitmaps originaux utilisés comme remplissages d'image
compare/ ← écrit par `figma-lens check` (report.md, side-by-side.png, diff.png)
figma-lens.map.json ← (racine du projet) composants/tokens Figma → code réel de ce projet
La commande figma-lens est l'interface en ligne de commande du dépôt figma-lens (npm link là-bas la rend globale ; sinon exécutez node <figma-lens>/bin/figma-lens.js …). Exécutez figma-lens help pour toutes les commandes.
S'il n'y a pas encore d'export
- Démarrez le pont pour le projet de l'utilisateur et laissez-le tourner (en arrière-plan) :
figma-lens bridge --out <projet>/design. - Demandez à l'utilisateur de sélectionner le(s) frame(s) dans Figma desktop → Plugins → Développement → Figma Lens → Exporter vers Claude.
- Lisez
design/INDEX.mdpour confirmer son arrivée.
Ne devinez jamais un design à partir d'une description quand un export est possible. Demandez l'export.
Flux de travail
- Lisez
spec.mdentièrement (il est petit). Notez la taille du frame, l'arbre de disposition, les variables et styles, et les ressources. - Regardez les images. Lisez
sections/*.pngune par une pour les détails. Utilisez[email protected]uniquement pour la vue d'ensemble, car les grandes images sont réduites lorsque vous les visualisez. Les valeurs proviennent toujours du spec, jamais en mesurant les pixels. - Utilisez la carte des composants (voir « Carte des composants » ci-dessous). Chaque
⇒ USE …dans spec.md est obligatoire : utilisez ce widget/composant et son mapping de props, et utilisez les tokens mappés ((color/primary ⇒ AppColors.primary)) au lieu des valeurs brutes. Pour les éléments marqués(not in map), recherchez dans le codebase le thème et les widgets existants (FlutterThemeData,AppColors,TextTheme, constantes d'espacement,lib/widgets; web variables CSS, configuration Tailwind,src/components). N'utilisez les valeurs brutes que lorsque aucun token n'existe, et indiquez à l'utilisateur celles que vous avez ajoutées. - Réutilisez les composants. Une couche marquée
component Button {Type=Primary}doit utiliser le widget ou composant Button existant du projet avec les props correspondantes. Créez-en un nouveau uniquement s'il n'en existe aucun, rendez-le réutilisable et ajoutez-le à la carte. - Construisez de haut en bas, en suivant l'arbre. Les mises en page automatiques correspondent à Row/Column/flex (tableaux ci-dessous). Seules les couches
@x,y(sans auto layout, ouabsolute) nécessitent Stack/Positioned ou un positionnement absolu. - Utilisez les fichiers exportés. Placez
assets/*.svgetimages/*dans le dossier de ressources du projet et référencez-les. Ne redessinez jamais les icônes et ne les remplacez jamais par un autre jeu d'icônes. - Copiez le texte exactement à partir de
"…"dans spec.md, y compris la casse, la ponctuation et les chaînes arabes/RTL. Un texte contenant plusieurs lignesrunnécessite un texte enrichi (TextSpan / spans imbriqués). - Vérifiez avec la boucle de comparaison et correction (ci-dessous). Ne déclarez pas un écran terminé en lisant le code ; prouvez-le avec un diff de capture d'écran.
Boucle de comparaison et correction
Après avoir construit un écran, comparez le résultat réel avec Figma et corrigez les différences :
- Exécutez la construction affichant cet écran :
- Web : démarrez le serveur de développement et ouvrez la route.
- Flutter web :
flutter run -d web-server --web-port 8080, puis utilisez cette URL. - Flutter Android : exécutez l'application sur un émulateur/appareil et naviguez vers l'écran.
- Vérifiez-le :
- Web / Flutter web :
figma-lens check --design design/<frame> --url http://localhost:<port>/<route>(la zone d'affichage est automatiquement réglée à la taille du frame et à l'échelle). - Android :
figma-lens check --design design/<frame> --adb --crop-top <barre d'état en px> [--crop-bottom <barre de navigation en px>](la barre d'état fait environ 24–48 dp × la densité de l'appareil ; rognez-la pour ne comparer que le contenu de l'application).
- Web / Flutter web :
- Lisez
design/<frame>/compare/report.md(pourcentage de différence, les plus grandes zones de différence et les identifiants de couche Figma dans chacune) et regardezcompare/side-by-side.png(Figma | build | diff, rouge = différent). - Corrigez de haut en bas : d'abord les différences de hauteur ou d'espacement (elles décalent tout ce qui suit), puis les tailles, couleurs, typographie, rayons, ombres. Recherchez chaque identifiant de couche listé dans spec.md pour les valeurs exactes.
- Relancez la vérification. Le rapport affiche le score précédent, vous pouvez donc voir la progression.
- Arrêtez-vous lorsque la différence est ≤ 3 % (l'anticrénelage du texte n'atteint jamais 0) ou lorsque deux exécutions consécutives cessent de s'améliorer. Signalez alors le score final et tout ce que vous n'avez pas pu faire correspondre, en expliquant pourquoi (police manquante, rendu de la plateforme, données dynamiques).
Règles : corrigez le vrai code, ne trichez jamais sur le diff (pas de captures d'écran ni d'images du design à l'intérieur de l'application). Utilisez les mêmes données/texte que le design lors de la vérification (données factices si nécessaire), sinon les différences de texte dominent le score.
Carte des composants
figma-lens.map.json (racine du projet) fait correspondre les composants et tokens Figma au code de ce projet :
{
"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)" } }
}
- Pas encore de carte → exécutez
figma-lens map --design design. Il ajoute chaque composant et token des exports avec des valeurs vides (""). - Remplissez-la vous-même : recherchez dans le codebase chaque composant (par nom, par apparence, par props) et chaque valeur de token ; écrivez
path#Symbol(ou une expression). Ne laissez""que lorsque rien n'existe encore."Prefix/*"correspond à toute une famille (icônes). - Après avoir modifié la carte, exécutez
figma-lens render design/<frame>pour que spec.md affiche les flèches⇒ USE. Le pont applique également la carte à chaque nouvel export. - Lorsque vous créez un nouveau widget réutilisable pour un composant non mappé, ajoutez-le à la carte.
Lecture de spec.md
- Les unités sont des px de design, qui sont équivalents aux px CSS et aux px logiques Flutter. Ne multipliez jamais par l'échelle de la capture d'écran.
row|column gap G pad t,r,b,l main:X cross:Yest une mise en page automatique.pad 16signifie que les quatre côtés font 16.gap autosignifie espace entre.w:FILL h:HUGdonne le dimensionnement sur chaque axe : FIXED (taille exacte indiquée), HUG (s'adapte au contenu) ou FILL (prend l'espace disponible).@x,yest la position à l'intérieur du parent.absolutesignifie un enfant positionné en absolu dans une mise en page automatique.pin H/Vdonne les contraintes (MIN = gauche/haut, MAX = droite/bas, STRETCH = les deux, CENTER, SCALE).bg A + Bliste les couches de peinture de bas en haut.(name)est une variable Figma et[style name]est un style Figma.- Les couleurs sont
#RRGGBBou#RRGGBBAA(alpha en dernier). - Le texte utilise le format
"chars" · Famille Style (poids) taille/hauteurDeLigne couleur. hauteurDeLigne est en px, % ou auto.lsest l'espacement des lettres en px, ou en % de la taille de police. radius 12définit tous les coins.radius tl,tr,br,bldéfinit chaque coin.smoothingest un lissage de coin de style iOS ; approximez-le, ou ignorez-le dans Flutter sauf si un package squircle est utilisé.drop-shadow x,y blur B spread S couleur,inner-shadow,layer-blur R,background-blur R.→ assets/x.svgsignifie que la couche est une icône. Utilisez le SVG à la taille indiquée.vars{itemSpacing=space/md}signifie que la propriété de la couche est liée à cette variable. Utilisez le token.
Mapping Flutter
| Figma | Flutter |
|---|---|
| column / row | Column / Row (utilisez spacing: G sur Flutter ≥ 3.27, sinon SizedBox pour les espacements) |
| wrap, rowGap R | Wrap(spacing: G, runSpacing: R) |
| pad t,r,b,l | Padding(padding: EdgeInsets.fromLTRB(l, t, r, b)). Attention à l'ordre. |
| main MIN / CENTER / MAX / SPACE_BETWEEN | MainAxisAlignment.start / center / end / spaceBetween |
| cross MIN / CENTER / MAX / BASELINE | CrossAxisAlignment.start / center / end / baseline (baseline nécessite textBaseline) |
| HUG sur l'axe principal | mainAxisSize: MainAxisSize.min |
| FILL sur l'axe principal du parent | Expanded (ou Flexible) ; grow N définit flex: N |
| FILL sur l'axe transversal du parent | CrossAxisAlignment.stretch sur le parent, ou SizedBox(width: double.infinity) |
| FIXED w×h | SizedBox(width: w, height: h) |
@x,y / absolute | Stack + Positioned(left: x, top: y) ; utilisez pin pour droite/bas |
| bg solid | BoxDecoration(color: Color(0xAARRGGBB)). Déplacez l'alpha au début : #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) en utilisant from/to dans spec.json |
| radius | BorderRadius.circular(r) ou BorderRadius.only(...) |
| border W color inside | Border.all(width: W, color: …) (inside est la valeur par défaut de Flutter ; center/outside nécessitent 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 (ou alpha de couleur pour les peintures) |
| text size/lineHeight | TextStyle(fontSize: size, height: lineHeight / size). Pour % : height: pct/100. Définissez leadingDistribution: TextLeadingDistribution.even pour correspondre au centrage vertical de 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. Pour les écrans RTL, utilisez TextAlign.start et EdgeInsetsDirectional. |
| assets/x.svg | SvgPicture.asset('assets/…/x.svg', width:, height:) (flutter_svg) |
| image fill FILL / FIT | Image.asset(fit: BoxFit.cover / BoxFit.contain) |
Mapping 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 (même ordre que 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 (ou simplement auto) |
| FILL (axe principal) | flex: 1 1 0; min-width: 0 |
| FILL (axe transversal) | align-self: stretch |
| FIXED | width: Wpx; height: Hpx; flex-shrink: 0 |
@x,y / absolute | parent position: relative ; enfant position:absolute; left:x; top:y (utilisez pin pour droite/bas) |
couleurs #RRGGBBAA | valide en CSS tel quel |
gradient-linear Ndeg | linear-gradient(Ndeg, c1 p1%, c2 p2%) |
| drop-shadow | box-shadow: x y B S color (inner-shadow ajoute inset) |
| layer-blur / background-blur R | filter: blur(R/2 px) / backdrop-filter: blur(R/2 px) |
| texte | 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> ou SVG en ligne ; conservez la taille exportée |
spec.json contient également le CSS généré par Figma lui-même (css) pour la plupart des couches. Utilisez-le pour recouper les valeurs CSS ; les tokens et la structure de disposition proviennent toujours de l'arbre ci-dessus.
Règles
- Les valeurs du spec priment sur ce que vous voyez dans l'image. En cas de conflit, faites confiance au spec et mentionnez le conflit.
- N'arrondissez pas et n'« améliorez » pas les valeurs (14 reste 14, pas 16) sauf si l'utilisateur demande une normalisation en tokens.
- Les couches cachées ne sont pas exportées. N'inventez pas d'états (survol, erreur, vide) qui ne figurent pas dans l'export ; demandez ces frames.
- Signalez les polices manquantes. Si la famille de polices n'est pas incluse dans le projet, dites-le et indiquez à l'utilisateur quoi ajouter (polices dans pubspec / @font-face).
- Pour plusieurs points de rupture ou états, demandez à l'utilisateur d'exporter chaque frame. Chacun devient son propre dossier.