HTML → Figma (Pixel-Perfect)
Convertit du HTML/CSS vanilla en frames Figma fidèles, en utilisant le MCP Figma (use_figma) pour écrire directement dans le fichier via l'API Plugin.
Étape 0 — Prérequis
Avant de commencer, vérifier que :
- L'utilisateur a fourni le code HTML (et le CSS associé — inline,
<style>, ou fichier séparé)
- L'utilisateur a fourni un lien Figma vers le fichier cible (ex.
https://www.figma.com/design/XXXXX/...)
- Si l'URL ne contient pas de
node-id, on travaillera sur la page courante
Si l'un de ces éléments manque, le demander avant de continuer.
Étape 1 — Parser le HTML/CSS
Analyser le code fourni pour extraire tous les éléments visuels dans l'ordre du DOM :
Extraction CSS
Pour chaque règle CSS, extraire :
background-color / background → fill de la frame
color → fill du texte
font-family, font-size, font-weight, line-height, letter-spacing → style texte
width, height, min-width, max-width → dimensions (convertir % en px si le conteneur parent est connu)
padding, margin → espacement (utiliser itemSpacing + paddingTop/Right/Bottom/Left dans Figma)
border-radius → cornerRadius
border → strokeWeight + strokeColor
box-shadow → effects de type DROP_SHADOW
display: flex → layoutMode: "HORIZONTAL" ou "VERTICAL" selon flex-direction
gap → itemSpacing
align-items / justify-content → primaryAxisAlignItems / counterAxisAlignItems
opacity → opacity
position: absolute + top/left/right/bottom → positionnement absolu dans la frame parente
Extraction des éléments
<div>, <section>, <header>, etc. → FRAME ou RECTANGLE
<p>, <h1>–<h6>, <span>, <a> → TEXT
<img> → RECTANGLE avec fill image (si src disponible) ou placeholder gris
<button> → FRAME avec texte enfant
<input>, <textarea> → FRAME avec stroke border
<hr> → LINE ou rectangle fin
<ul>/<li> → groupe de TEXT nodes avec bullet
<svg> → utiliser figma.createNodeFromSvg() avec le SVG inline
Étape 2 — Résoudre les unités CSS
Avant de passer au code Figma, convertir toutes les unités :
| CSS |
Figma |
px |
valeur directe (1px = 1 unité Figma) |
rem |
multiplier par 16 (base par défaut) |
em |
multiplier par la font-size parente |
% |
calculer par rapport au conteneur parent connu |
vw / vh |
utiliser la largeur/hauteur de la frame racine |
auto |
ignorer ou utiliser "HUG" si c'est width/height |
inherit |
remonter à la valeur parente |
Pour les couleurs :
hex → décomposer en { r, g, b } (valeurs 0–1 : diviser par 255)
rgb() / rgba() → même chose + opacity pour le a
hsl() → convertir en RGB d'abord
var(--custom-prop) → résoudre la variable CSS si elle est définie dans le code fourni
Étape 3 — Générer le code Plugin Figma
Construire un script JavaScript complet pour use_figma. Structure type :
// Helper : convertit hex en RGB normalisé
function hexToRgb(hex) {
const r = parseInt(hex.slice(1,3), 16) / 255;
const g = parseInt(hex.slice(3,5), 16) / 255;
const b = parseInt(hex.slice(5,7), 16) / 255;
return { r, g, b };
}
// Helper : charge et attend une font
async function loadFont(family, style) {
try {
await figma.loadFontAsync({ family, style });
} catch(e) {
await figma.loadFontAsync({ family: "Inter", style: "Regular" });
}
}
// Frame racine (représente le <body> ou le conteneur principal)
const frame = figma.createFrame();
frame.name = "Page – [nom du composant]";
frame.resize(1440, 900); // adapter à la vraie taille
frame.fills = [{ type: 'SOLID', color: hexToRgb('#ffffff') }];
frame.x = 0;
frame.y = 0;
// Pour chaque enfant…
// (générer récursivement)
Règles impératives pour le code généré
- Toujours charger les fonts avec
await figma.loadFontAsync() avant de manipuler du texte — sinon l'API crashe
- Nommer les nodes selon la structure HTML :
"div.card", "h1 – Titre", "button – CTA", etc.
- Appender dans le bon ordre : toujours
parent.appendChild(child) après avoir créé le nœud
- Auto-layout : si le CSS contient
display: flex, utiliser layoutMode + primaryAxisSizingMode + counterAxisSizingMode
- Effets :
box-shadow → créer un objet { type: "DROP_SHADOW", color: {..., a: opacity}, offset: {x, y}, radius: blur, visible: true, blendMode: "NORMAL" }
- Coins arrondis :
cornerRadius accepte un seul nombre ; pour des coins différents, utiliser topLeftRadius, topRightRadius, etc.
- Stroke :
strokeWeight + strokes = [{ type: 'SOLID', color: ... }] + strokeAlign: "INSIDE" | "OUTSIDE" | "CENTER"
- Images : créer un rectangle et utiliser
figma.createImage() avec une URL si disponible, ou un placeholder fills = [{ type: 'SOLID', color: { r: 0.9, g: 0.9, b: 0.9 } }]
- SVG inline : utiliser
figma.createNodeFromSvg(svgString) — le seul moyen correct
- Wrap dans
(async () => { ... })() si des await sont utilisés (ce qui est presque toujours le cas pour les fonts)
Étape 4 — Appeler use_figma
Une fois le script généré :
Figma:use_figma({
fileKey: "<extrait de l'URL>",
description: "Création pixel-perfect de [nom] depuis HTML/CSS",
code: "<le script généré>"
})
Si le code est très long (>300 lignes), le découper en appels successifs :
- D'abord la frame racine + les sections principales
- Ensuite les composants enfants section par section
Étape 5 — Vérification & corrections
Après l'appel, informer l'utilisateur du résultat et proposer :
- "Les couleurs sont décalées" → re-vérifier la conversion hex/rgb
- "Le texte est trop grand/petit" → re-vérifier la conversion
rem/em
- "Les espacements ne correspondent pas" → vérifier
padding vs itemSpacing (auto-layout) vs positionnement absolu
- "Une section manque" → relancer un appel
use_figma pour l'ajouter
Limites connues & workarounds
| Limitation |
Workaround |
background: linear-gradient() |
Figma supporte les gradients : utiliser { type: "GRADIENT_LINEAR", gradientStops: [...], gradientTransform: [[...]] } |
background-image: url(...) |
Créer l'image via figma.createImage() si l'URL est accessible, sinon placeholder |
clip-path |
Non supporté nativement — utiliser une mask layer |
transform: rotate() |
node.rotation = angle (en degrés, sens antihoraire dans Figma) |
::before / ::after |
Créer un nœud Figma séparé représentant le pseudo-élément |
overflow: hidden |
node.clipsContent = true |
| Fonts non Google |
Fallback sur Inter si la font n'est pas dispo dans Figma |
position: sticky |
Traiter comme position: relative pour la maquette statique |
animation / transition |
Ignorer — les maquettes sont statiques |
calc() |
Résoudre manuellement avant de passer la valeur |
Format de réponse attendu
Après chaque import réussi :
- Confirmer que les frames ont été créées
- Indiquer le nom exact des frames dans Figma
- Proposer des ajustements si des éléments n'ont pas pu être convertis fidèlement
- Rappeler à l'utilisateur d'ouvrir Figma pour voir le résultat (le MCP ne fournit pas de preview)
1---2name: html-to-figma3description: HTML → Figma (Pixel-Perfect)4---56# HTML → Figma (Pixel-Perfect)78Convertit du HTML/CSS vanilla en frames Figma fidèles, en utilisant le MCP Figma (`use_figma`) pour écrire directement dans le fichier via l'API Plugin.910---1112## Étape 0 — Prérequis1314Avant de commencer, vérifier que :151. L'utilisateur a fourni le code HTML (et le CSS associé — inline, `<style>`, ou fichier séparé)162. L'utilisateur a fourni un **lien Figma** vers le fichier cible (ex. `https://www.figma.com/design/XXXXX/...`)173. Si l'URL ne contient pas de `node-id`, on travaillera sur la page courante1819Si l'un de ces éléments manque, le demander avant de continuer.2021---2223## Étape 1 — Parser le HTML/CSS2425Analyser le code fourni pour extraire **tous** les éléments visuels dans l'ordre du DOM :2627### Extraction CSS28Pour chaque règle CSS, extraire :29- `background-color` / `background` → fill de la frame30- `color` → fill du texte31- `font-family`, `font-size`, `font-weight`, `line-height`, `letter-spacing` → style texte32- `width`, `height`, `min-width`, `max-width` → dimensions (convertir `%` en px si le conteneur parent est connu)33- `padding`, `margin` → espacement (utiliser `itemSpacing` + `paddingTop/Right/Bottom/Left` dans Figma)34- `border-radius` → `cornerRadius`35- `border` → `strokeWeight` + `strokeColor`36- `box-shadow` → `effects` de type `DROP_SHADOW`37- `display: flex` → `layoutMode: "HORIZONTAL"` ou `"VERTICAL"` selon `flex-direction`38- `gap` → `itemSpacing`39- `align-items` / `justify-content` → `primaryAxisAlignItems` / `counterAxisAlignItems`40- `opacity` → `opacity`41- `position: absolute` + `top/left/right/bottom` → positionnement absolu dans la frame parente4243### Extraction des éléments44- `<div>`, `<section>`, `<header>`, etc. → **FRAME** ou **RECTANGLE**45- `<p>`, `<h1>`–`<h6>`, `<span>`, `<a>` → **TEXT**46- `<img>` → **RECTANGLE** avec fill image (si src disponible) ou placeholder gris47- `<button>` → **FRAME** avec texte enfant48- `<input>`, `<textarea>` → **FRAME** avec stroke border49- `<hr>` → **LINE** ou rectangle fin50- `<ul>/<li>` → groupe de TEXT nodes avec bullet51- `<svg>` → utiliser `figma.createNodeFromSvg()` avec le SVG inline5253---5455## Étape 2 — Résoudre les unités CSS5657Avant de passer au code Figma, convertir toutes les unités :5859| CSS | Figma |60|-----|-------|61| `px` | valeur directe (1px = 1 unité Figma) |62| `rem` | multiplier par 16 (base par défaut) |63| `em` | multiplier par la font-size parente |64| `%` | calculer par rapport au conteneur parent connu |65| `vw` / `vh` | utiliser la largeur/hauteur de la frame racine |66| `auto` | ignorer ou utiliser `"HUG"` si c'est width/height |67| `inherit` | remonter à la valeur parente |6869Pour les couleurs :70- `hex` → décomposer en `{ r, g, b }` (valeurs 0–1 : diviser par 255)71- `rgb()` / `rgba()` → même chose + `opacity` pour le `a`72- `hsl()` → convertir en RGB d'abord73- `var(--custom-prop)` → résoudre la variable CSS si elle est définie dans le code fourni7475---7677## Étape 3 — Générer le code Plugin Figma7879Construire un script JavaScript complet pour `use_figma`. Structure type :8081```javascript82// Helper : convertit hex en RGB normalisé83function hexToRgb(hex) {84 const r = parseInt(hex.slice(1,3), 16) / 255;85 const g = parseInt(hex.slice(3,5), 16) / 255;86 const b = parseInt(hex.slice(5,7), 16) / 255;87 return { r, g, b };88}8990// Helper : charge et attend une font91async function loadFont(family, style) {92 try {93 await figma.loadFontAsync({ family, style });94 } catch(e) {95 await figma.loadFontAsync({ family: "Inter", style: "Regular" });96 }97}9899// Frame racine (représente le <body> ou le conteneur principal)100const frame = figma.createFrame();101frame.name = "Page – [nom du composant]";102frame.resize(1440, 900); // adapter à la vraie taille103frame.fills = [{ type: 'SOLID', color: hexToRgb('#ffffff') }];104frame.x = 0;105frame.y = 0;106107// Pour chaque enfant…108// (générer récursivement)109```110111### Règles impératives pour le code généré1121131. **Toujours charger les fonts** avec `await figma.loadFontAsync()` avant de manipuler du texte — sinon l'API crashe1142. **Nommer les nodes** selon la structure HTML : `"div.card"`, `"h1 – Titre"`, `"button – CTA"`, etc.1153. **Appender dans le bon ordre** : toujours `parent.appendChild(child)` après avoir créé le nœud1164. **Auto-layout** : si le CSS contient `display: flex`, utiliser `layoutMode` + `primaryAxisSizingMode` + `counterAxisSizingMode`1175. **Effets** : `box-shadow` → créer un objet `{ type: "DROP_SHADOW", color: {..., a: opacity}, offset: {x, y}, radius: blur, visible: true, blendMode: "NORMAL" }`1186. **Coins arrondis** : `cornerRadius` accepte un seul nombre ; pour des coins différents, utiliser `topLeftRadius`, `topRightRadius`, etc.1197. **Stroke** : `strokeWeight` + `strokes = [{ type: 'SOLID', color: ... }]` + `strokeAlign: "INSIDE" | "OUTSIDE" | "CENTER"`1208. **Images** : créer un rectangle et utiliser `figma.createImage()` avec une URL si disponible, ou un placeholder `fills = [{ type: 'SOLID', color: { r: 0.9, g: 0.9, b: 0.9 } }]`1219. **SVG inline** : utiliser `figma.createNodeFromSvg(svgString)` — le seul moyen correct12210. **Wrap dans `(async () => { ... })()`** si des `await` sont utilisés (ce qui est presque toujours le cas pour les fonts)123124---125126## Étape 4 — Appeler `use_figma`127128Une fois le script généré :129130```131Figma:use_figma({132 fileKey: "<extrait de l'URL>",133 description: "Création pixel-perfect de [nom] depuis HTML/CSS",134 code: "<le script généré>"135})136```137138Si le code est très long (>300 lignes), le découper en appels successifs :1391. D'abord la frame racine + les sections principales1402. Ensuite les composants enfants section par section141142---143144## Étape 5 — Vérification & corrections145146Après l'appel, informer l'utilisateur du résultat et proposer :147- **"Les couleurs sont décalées"** → re-vérifier la conversion hex/rgb148- **"Le texte est trop grand/petit"** → re-vérifier la conversion `rem`/`em`149- **"Les espacements ne correspondent pas"** → vérifier `padding` vs `itemSpacing` (auto-layout) vs positionnement absolu150- **"Une section manque"** → relancer un appel `use_figma` pour l'ajouter151152---153154## Limites connues & workarounds155156| Limitation | Workaround |157|------------|------------|158| `background: linear-gradient()` | Figma supporte les gradients : utiliser `{ type: "GRADIENT_LINEAR", gradientStops: [...], gradientTransform: [[...]] }` |159| `background-image: url(...)` | Créer l'image via `figma.createImage()` si l'URL est accessible, sinon placeholder |160| `clip-path` | Non supporté nativement — utiliser une mask layer |161| `transform: rotate()` | `node.rotation = angle` (en degrés, sens antihoraire dans Figma) |162| `::before / ::after` | Créer un nœud Figma séparé représentant le pseudo-élément |163| `overflow: hidden` | `node.clipsContent = true` |164| Fonts non Google | Fallback sur Inter si la font n'est pas dispo dans Figma |165| `position: sticky` | Traiter comme `position: relative` pour la maquette statique |166| `animation / transition` | Ignorer — les maquettes sont statiques |167| `calc()` | Résoudre manuellement avant de passer la valeur |168169---170171## Format de réponse attendu172173Après chaque import réussi :1741. Confirmer que les frames ont été créées1752. Indiquer le nom exact des frames dans Figma1763. Proposer des ajustements si des éléments n'ont pas pu être convertis fidèlement1774. Rappeler à l'utilisateur d'ouvrir Figma pour voir le résultat (le MCP ne fournit pas de preview)