Tu lances un workflow Figma → code avec une IA, tu penses gagner du temps… et tu te retrouves avec des composants « proches » mais pas les tiens. Un bouton qui n’est pas ton bouton. Un espacement qui ressemble à ton spacing scale, mais qui invente un 14px au milieu. Un Card qui n’existe pas dans ton design system, mais que l’IA a jugé « logique ».
Le problème n°1, ce n’est pas que le rendu est moche. C’est que ce n’est plus fiable. Et tant que ce n’est pas fiable, tu ne peux pas industrialiser. Mon angle est simple : si tu veux du design-to-code qui tient en équipe, il te faut un contrat. Une source de vérité exploitable par la machine, et une règle brutale : si ce n’est pas dans le design system, c’est une erreur.
Pourquoi l’IA « hallucine » ton design system (même avec une belle librairie Figma)
On met souvent ça sur le dos des « hallucinations » comme si c’était de la magie noire. En réalité, c’est souvent un problème beaucoup plus terre à terre : il n’y a pas de source de vérité que l’IA peut appliquer sans interpréter.
Figma, c’est excellent pour dessiner, itérer, présenter. Mais pour générer du code, l’IA a besoin d’objets stables et nommés. Des composants identifiables, des variants, des props, des tokens. Si à l’écran elle voit « un bouton bleu avec un coin arrondi », elle doit deviner si ça correspond à <Button variant="primary" size="md" /> ou à un bouton ad hoc. Devine ce qu’elle fait : elle improvise. Et elle improvise d’autant plus qu’elle veut te « satisfaire » avec un résultat complet.
Le piège, c’est que même quand tu as une librairie Figma bien rangée, l’IA peut ne pas la réutiliser correctement. Certains outils reproduisent des styles (couleurs, variables, typos) mais ratent l’objectif principal : réemployer tes composants existants. Résultat : tu obtiens une copie visuelle, mais un codebase qui diverge dès le premier écran.
Sponsorisé par Le Scribouillard
Besoin de contenu optimisé SEO ?
Utilisez la meilleure plateforme française de création de contenu assistée par IA ! Et générez des articles pour moins de 1€ !
Le bon modèle mental : design-to-code = compilation, pas traduction
Si tu veux que ça scale, arrête de voir ça comme « l’IA traduit une maquette en code ». Vois plutôt ça comme un compilateur : il prend une entrée, et il n’a le droit de produire une sortie que si les symboles existent.
Dans un compilateur, quand tu appelles une fonction qui n’existe pas, tu n’obtiens pas « une fonction à peu près équivalente ». Tu obtiens une erreur. C’est exactement l’état d’esprit à adopter : l’IA ne doit pas « créer » un composant parce que ça l’arrange. Elle doit référencer un composant connu. Sinon, elle s’arrête, et elle te dit quoi ajouter au design system ou au mapping.
Ça paraît dur, mais c’est ce qui rend le workflow viable. Tant que l’IA peut bricoler, tu auras une dette UI silencieuse. Et elle est vicieuse : tu la vois deux semaines plus tard, quand tu as 6 boutons différents en prod et une équipe qui se demande lequel est « le vrai ».
Le « contrat » qui change tout : un manifest strict de composants (et pas un PDF de guidelines)
Les guidelines en doc, les captures, même un joli Notion, ça aide les humains. Pour une IA, c’est trop flou. Ce qu’il te faut, c’est un manifest lisible par une machine qui décrit explicitement ce qui est autorisé : composants, props, variants, valeurs possibles, et comportements par défaut.
Je parle d’un truc bête : un JSON versionné dans ton repo, revu en PR comme le reste. Et surtout, avec une règle claire côté génération : si le manifest ne permet pas de représenter l’UI demandée, l’IA doit échouer.
{
"$schema": "https://example.com/ui-manifest.schema.json",
"version": "1.3.0",
"library": {
"name": "@acme/ui",
"framework": "react"
},
"tokens": {
"source": "./tokens/tokens.json",
"mapping": {
"color.primary": "var(--color-primary)",
"radius.md": "var(--radius-md)",
"space.4": "var(--space-4)"
},
"rule": "unknown-token-is-error"
},
"components": {
"Button": {
"import": "@acme/ui/Button",
"props": {
"variant": {"type": "enum", "values": ["primary", "secondary", "ghost"], "required": true},
"size": {"type": "enum", "values": ["sm", "md", "lg"], "default": "md"},
"icon": {"type": "enum", "values": ["none", "leading", "trailing"], "default": "none"},
"disabled": {"type": "boolean", "default": false}
}
},
"Card": {
"import": "@acme/ui/Card",
"props": {
"elevation": {"type": "enum", "values": ["none", "sm", "md"], "default": "sm"},
"padding": {"type": "token", "values": ["space.4", "space.6"], "default": "space.4"}
}
}
},
"generationRules": {
"unknown-component-is-error": true,
"unknown-prop-is-error": true,
"unknown-enum-value-is-error": true,
"inline-styles": "forbidden"
}
}
Ce manifest n’a rien de « magique ». Il est même volontairement frustrant : il limite. Mais c’est son boulot. Si l’IA veut te sortir un <SuperButton />, elle ne peut pas. Si elle veut un variant="tertiary", elle se prend un mur. Et ce mur t’évite une UI qui diverge en silence.
Tokens et Storybook : où mettre la vérité (spoiler : pas dans des screenshots Figma)
On voudrait que Figma soit l’unique source de vérité. Dans les faits, dès que tu touches au design-to-code, ça devient dangereux. Parce que la vérité qui compte, ce n’est pas « à quoi ça ressemble sur une frame », c’est comment ça se consomme en code.
Mon setup préféré quand on veut être sérieux : les design tokens vivent dans le repo (ou dans un package dédié), versionnés, testés, publiés. Et la vérité des composants vit dans Storybook (ou un équivalent) avec des stories qui couvrent les variants réels. Figma reste très utile, mais plutôt comme un outil d’usage et de conception. La machine, elle, a besoin d’API stables.
Concrètement, ça veut dire que l’IA ne doit pas « inférer » que ton border-radius est 10px parce qu’elle voit un arrondi. Elle doit mapper vers radius.md. Et si elle n’y arrive pas, elle doit te remonter « je ne peux pas exprimer ce design avec vos tokens actuels ». Ce feedback est précieux, parce qu’il déclenche la bonne discussion : est-ce qu’on étend le design system, ou est-ce qu’on ajuste le design ?
La règle qui rend le workflow utilisable : « si absent → erreur »
Je le redis parce que c’est le point pivot : tu veux des erreurs, pas des compromis silencieux. En équipe, un compromis silencieux se transforme en dette. Une erreur, ça se traite. Ça se discute. Ça se corrige. Ça se tracke.
Cette règle doit s’appliquer partout : composant inconnu, prop inconnue, token inconnu, valeur de variant inventée, style inline qui n’existe pas dans le système. Tant que tu laisses une porte ouverte (« ok, tu peux mettre un style={{ padding: 14 }} si tu veux »), l’IA va l’emprunter. Pas parce qu’elle est mauvaise. Parce que c’est la manière la plus simple de « réussir » la tâche.
Au passage, c’est aussi une bonne hygiène côté humain. Beaucoup de design systems se dégradent parce que les devs ont la possibilité de « patcher » vite fait avec du CSS local. Là, tu formalises le fait que l’UI est un produit, pas un collage.
Un garde-fou très simple : valider le code généré avant de le merger
Le manifest ne sert à rien si tu ne l’appliques pas. Le truc concret à mettre en place, c’est un validateur dans ton pipeline (ou au minimum un script de check) qui refuse un output hors contrat. Pas besoin d’IA ici, juste une vérification froide.
Exemple minimaliste : tu analyses l’AST (ou tu fais un check plus naïf si tu veux aller vite) et tu refuses les imports hors librairie UI, les styles inline, et les props qui ne matchent pas le manifest. Même un contrôle imparfait améliore radicalement la situation, parce qu’il enlève l’option « on verra plus tard ».
import manifest from "./ui-manifest.json";
type Violation = { file: string; message: string };
export function validateGeneratedUI(files: Array<{ path: string; code: string }>): Violation[] {
const violations: Violation[] = [];
for (const f of files) {
if (f.code.includes("style={{")) {
violations.push({ file: f.path, message: "Inline styles are forbidden by ui-manifest" });
}
// Exemple volontairement simple : on force l'import depuis @acme/ui
const importRegex = /from\s+["']([^"']+)["']/g;
let m: RegExpExecArray | null;
while ((m = importRegex.exec(f.code))) {
const mod = m[1];
if (mod.startsWith("@acme/ui/") || mod === "@acme/ui") continue;
if (mod.startsWith("react") || mod.startsWith("next")) continue;
violations.push({ file: f.path, message: `Unexpected import: ${mod}` });
}
}
return violations;
}
Oui, ce check est incomplet. Mais c’est précisément ce que je recommande en premier : un garde-fou qui attrape les gros écarts, sans transformer ton projet en thèse. Ensuite tu raffines avec un vrai parsing, des règles sur les composants, et des exceptions assumées (et documentées) quand il y a une bonne raison.
Ce que je mets en place côté équipe pour éviter la dérive (sans bureaucratie)
Le design-to-code IA a un effet pervers : il augmente la cadence, donc il augmente le volume de changements UI. Si tu gardes le même niveau de review qu’avant, tu vas laisser passer des divergences parce que « ça ressemble à la maquette ». Ce n’est pas suffisant.
Dans la pratique, je fais trois choses. Un template de PR qui force à préciser « quels composants du DS sont utilisés » et « quels tokens ont été touchés » (ça évite le change vague « UI update »). Une review UI assumée, pas forcément longue, mais avec quelqu’un qui sait repérer une dérive de variants ou une nouvelle règle inventée. Et des tests qui attrapent les régressions bêtes : snapshots visuels si tu as déjà l’infra, et tests d’accessibilité sur les composants critiques. Rien d’exotique, juste de quoi rendre la dérive coûteuse à laisser passer.
Ça marche aussi très bien avec une règle sociale simple : si l’IA te demande « je peux créer un composant X ? », la réponse par défaut est non. On crée un ticket design system. On décide. On versionne. On publie. On avance. Sinon, tu construis une UI « liquide » qui change de forme à chaque génération.
Faire évoluer le manifest sans le transformer en prison
Le risque, quand on entend « contrat strict », c’est de tomber dans le design system qui bloque tout. Ce n’est pas le but. Le but, c’est de rendre l’évolution explicite.
Je conseille de versionner le manifest comme une API. Quand tu ajoutes un composant ou un variant, tu augmentes la version. Quand tu supprimes ou changes une prop, tu fais une breaking change. Et surtout, tu gardes une trace claire de pourquoi ça a évolué. Ça évite les discussions infinies du type « je croyais que ça existait ». Non. Ça existe ou ça n’existe pas, et c’est écrit.
Et si tu as un produit en mouvement constant, tu peux totalement garder une porte d’évolution rapide, mais contrôlée. Par exemple, autoriser un petit espace « expérimental » dans le manifest, avec un préfixe explicite, une durée de vie courte, et un rappel automatique si ça traîne. L’important, c’est que même l’expérimental soit nommé, versionné, et visible.
Mon avis (assez tranché) : si ton IA peut improviser, ce n’est pas un workflow
Je vois passer beaucoup de démos où « ça marche »… tant que tu regardes le screenshot final. En prod, tu ne ship pas des screenshots. Tu ship des composants, des dépendances, des conventions, des tests, des migrations, de la maintenance. Si l’IA n’est pas enfermée dans un cadre strict, elle va te donner une UI qui te plaît aujourd’hui et qui te coûte cher demain.
La bonne nouvelle, c’est que la solution n’est pas un modèle plus intelligent. C’est de l’ingénierie produit classique : une source de vérité, un contrat, et des garde-fous. Une IA peut être très bonne dans ce cadre. Mais il faut accepter un truc contre-intuitif : pour gagner du temps, tu dois lui interdire d’être « créative ».