Aller au contenu principal
Site en cours de refonte — quelques pages peuvent bouger ou évoluer.
31 mars 2026

Figma → DTCG → code : le pipeline sobre qui évite la dérive

Figma ne te sort pas un DTCG « prêt CI » d’un claquement de doigts. Voilà une chaîne reproductible pour extraire, normaliser, versionner et diffuser des tokens sans plugin magique.

12 min de lecture
87 vues
réactions
Partager :
Figma → DTCG → code : le pipeline sobre qui évite la dérive

Si tu cherches « exporter des design tokens DTCG depuis Figma », tu vas vite tomber sur des réponses qui sentent le piège : un plugin, un bouton “export”, et bonne chance pour l’automatiser en CI. Dans la vraie vie, tu veux l’inverse : un pipeline headless, versionné, rejouable, qui transforme une librairie Figma en artefacts stables (CSS variables, JSON, TS) sans drift entre design et code.

Le point de friction est simple et je le pose tout de suite : Figma ne te livre pas un DTCG “prêt prod” dans un format que tu peux plugger directement dans un build. Tu récupères des données (variables, collections, modes, aliases), et ensuite c’est à toi de faire le boulot ingrat : naming, structure, résolution d’aliases, séparation light/dark, et génération des sorties qui intéressent vraiment ton front.

Pourquoi Figma ne t’exporte pas un DTCG “prêt CI” (et pourquoi c’est normal)

Le DTCG (Design Tokens Community Group) te donne un format d’échange. Figma, lui, te donne un modèle de données pensé pour designer : des collections, des modes, des variables qui peuvent référencer d’autres variables, et des valeurs qui ne collent pas toujours à tes contraintes d’implémentation.

Du coup, quand tu demandes “un export DTCG”, tu mélanges deux besoins : récupérer la vérité depuis Figma (ça, l’API peut le faire), et sortir une représentation normée et exploitable (ça, c’est ton job, ou celui d’un outil de transformation que tu maîtrises). Les plugins “magiques” masquent juste cette étape. Le jour où tu veux le faire tourner sur un runner GitHub Actions, ou verrouiller une règle de naming, tu retombes sur le réel.

Le choix qui change tout : où est la source de vérité ?

Avant de parler API, parle gouvernance. Tu as deux modèles qui marchent.

Le premier, c’est Figma comme source-of-truth. Les designers modifient les variables. Ton CI extrait, normalise, génère et publie. Le repo devient un miroir versionné (avec PR et diff) mais pas l’endroit où on “édite” les tokens. C’est le modèle le plus simple si tu as déjà une culture design system côté design, et que tu veux que les changements partent du design.

Le deuxième, c’est le repo comme source-of-truth et Figma comme consommateur. Honnêtement, sans Enterprise et sans features de sync “officielles” vraiment propres, c’est vite pénible. Je ne dis pas que c’est impossible, je dis que tu vas dépenser beaucoup d’énergie à gérer des collisions et des workflows tordus. Si ton objectif c’est “arrêter la dérive”, je préfère le premier modèle, avec des garde-fous côté code (diff + review) qui rendent la boucle robuste.

Extraction headless : récupérer les Variables via l’API Figma

Pour faire tourner ça en CI, tu as besoin d’un appel API stable, d’un token d’accès (PAT ou token de service selon ton contexte), et d’un identifiant de fichier. L’idée n’est pas de scraper une UI, ni d’exécuter un plugin. Tu veux juste récupérer la matière première : collections, variables, modes, valeurs, références.

Un point terrain : ce que tu récupères ressemble rarement à ce que tu veux committer. Les noms peuvent être “beaux” pour Figma (avec des slashes, des espaces), les valeurs peuvent être typées façon Figma (couleurs en RGBA, nombres sans unité), et les aliases sont souvent partout. Donc tu assumes dès le départ qu’il y a une étape de transformation.

// scripts/pull-figma-variables.js
// Exemple volontairement simple : l'idée est le pipeline, pas le SDK.

import fs from "node:fs/promises";

const FILE_KEY = process.env.FIGMA_FILE_KEY;
const FIGMA_TOKEN = process.env.FIGMA_TOKEN;

if (!FILE_KEY || !FIGMA_TOKEN) {
  throw new Error("Missing FIGMA_FILE_KEY or FIGMA_TOKEN");
}

const url = `https://api.figma.com/v1/files/${FILE_KEY}/variables/local`;
const res = await fetch(url, {
  headers: {
    "X-Figma-Token": FIGMA_TOKEN,
  },
});

if (!res.ok) {
  const text = await res.text();
  throw new Error(`Figma API error ${res.status}: ${text}`);
}

const payload = await res.json();
await fs.mkdir(".cache", { recursive: true });
await fs.writeFile(".cache/figma-variables.json", JSON.stringify(payload, null, 2));

console.log("Saved .cache/figma-variables.json");

Ce fichier .cache/figma-variables.json ne doit pas forcément être committé tel quel. Je m’en sers comme artefact brut pour débugger, et comme input de la transformation. En CI, c’est parfait : tu peux reproduire exactement la même transformation à partir du même input, et tu vois vite ce qui change.

Du JSON Figma au DTCG propre : naming, types, structure

Le DTCG, ce n’est pas “un JSON au pif”. C’est un contrat. Ton pipeline doit donc faire trois choses sans discuter.

D’abord, normaliser les noms. Les slashes Figma (genre color/brand/primary) sont top pour l’UI. Côté tokens, je préfère les convertir en structure (objets imbriqués) ou en clés dot-notation, mais je garde une règle unique et je m’y tiens. Ce n’est pas une question de goût, c’est une question de stabilité des imports et de diff lisible en PR.

Ensuite, imposer les types. Figma sait que c’est une couleur, un nombre, un string. Toi tu veux des $type cohérents (color, dimension, fontFamily, fontWeight, duration…), sinon tu vas générer du CSS bizarre ou des typings TS inutiles.

Enfin, sortir une structure DTCG qui te sert vraiment. Perso, je garde les descriptions quand elles existent, et j’ajoute un $extensions minimal pour conserver une trace de l’origine (id Figma, collection, mode). Ça aide énormément quand quelqu’un te dit “pourquoi ce token est là ?” ou “c’est quoi la source ?”.

Le vrai sujet : modes (light/dark) et aliases, sans te faire exploser la CI

Les modes, c’est le point où beaucoup de “pipelines tokens” deviennent fragiles. En design, c’est normal d’avoir light/dark, high-contrast, ou même des thèmes marque. En code, tu dois choisir comment tu l’exprimes.

Mon approche la plus robuste, c’est de générer un fichier DTCG par mode pour les valeurs finales (par exemple tokens.light.json et tokens.dark.json), plus un fichier “catalogue” si tu veux garder une vue globale. Pourquoi ? Parce que ça évite de bricoler une notion de “mode” qui n’est pas universelle dans tous les consumers. Pour le front, c’est limpide : tu charges un set ou tu swaps les variables selon le thème.

Les aliases, eux, sont un cadeau empoisonné. Si tu exportes “tel quel”, tu vas publier des références qui cassent dès que tu renommes un token, ou qui s’expriment différemment selon les outils. Donc je tranche : soit tu résous les aliases au moment de générer le set final (tu publies des valeurs concrètes), soit tu les gardes mais tu dois garantir la stabilité du graphe de références. Dans la plupart des équipes, résoudre pour les sorties (CSS/TS) et garder la trace des références en interne est le meilleur compromis.

Générer des sorties utiles : CSS variables, TypeScript, Tailwind

Le DTCG est une base d’échange. Mais tes apps ne consomment pas “du DTCG” en prod. Elles consomment du CSS, du JS/TS, parfois une config Tailwind, parfois des tokens pour React Native. Ton pipeline doit donc produire des artefacts adaptés à ton runtime.

Pour le web, je commence presque toujours par des CSS custom properties. C’est le format le plus interopérable, et ça marche avec tout : Tailwind, CSS Modules, styled-components, vanilla CSS, même un vieux stack server-rendered.

/* dist/tokens.light.css */
:root {
  --color-brand-primary: #3b82f6;
  --color-text-default: #111827;
  --radius-md: 8px;
  --space-3: 12px;
}

/* dist/tokens.dark.css */
[data-theme="dark"] {
  --color-brand-primary: #60a5fa;
  --color-text-default: #f9fafb;
}

Ensuite, je génère du TypeScript. Pas pour “faire joli”, mais pour éviter les accès stringly-typed partout, et pour rendre les refactors moins dangereux. Deux stratégies marchent : soit tu génères un objet tokens typed, soit tu génères juste des types (union de clés) et tu laisses le runtime sur CSS variables. Dans beaucoup de projets, je préfère le runtime CSS (simple) et le TS pour guider l’usage.

Pour Tailwind, même idée : tu peux mapper des tokens vers theme.extend. Je le fais uniquement quand ça apporte une vraie valeur (exposer une palette, des radii, un spacing scale). Si ton équipe a tendance à “inventer” des valeurs dans Tailwind, c’est un bon levier. Si ton équipe est déjà disciplinée avec des CSS variables, ça peut être un détour.

Versionner les tokens : SemVer, changelog, et PR lisibles

Le jour où tu publies des tokens, tu publies une API. Donc oui, ça mérite du SemVer. Et non, “on va y penser plus tard” n’est pas un plan.

Concrètement, je veux que le pipeline crée un artefact versionné (package npm privé, repo dédié, ou simple dossier versionné dans un monorepo) et que chaque changement passe par une PR avec un diff lisible. Si tu committes du JSON non déterministe (ordre des clés qui bouge, arrondis qui varient), tu vas souffrir. Prends le temps de stabiliser la sérialisation, sinon tu vas perdre la revue humaine dans du bruit.

Pour le changelog, je suis pragmatique : soit tu forces une discipline type changesets, soit tu génères un résumé automatique des changements (tokens ajoutés, supprimés, modifiés) et tu demandes à la PR d’avoir une phrase humaine. L’objectif n’est pas d’écrire de la poésie. L’objectif est que l’équipe front sache si c’est un patch safe ou un breaking change qui va casser des écrans.

Garde-fous anti-drift : le pipeline ne suffit pas, il faut des tests

Le pipeline te donne de la reproductibilité. Mais la dérive design/code, elle, arrive surtout quand un changement “semble petit” et déclenche des effets en cascade.

Le garde-fou le plus rentable, c’est un diff de tokens en CI qui te sort quelque chose de lisible. Pas un blob de JSON. Un vrai résumé. Quand tu vois « color.text.default change de #111827 à #0b1220 », tu sais immédiatement si c’est normal ou si quelqu’un a cliqué au mauvais endroit.

Ensuite, si tu as un Storybook, le combo snapshots visuels (Chromatic ou équivalent) + tokens versionnés est redoutable. Tu merges une PR tokens, tu vois les diffs UI, tu décides. Ça évite les “on verra en recette” qui finissent en vendredi 18h.

Enfin, sur les couleurs, je mets un garde-fou accessibilité minimal. Pas besoin d’un audit WCAG complet à chaque commit, mais au moins vérifier quelques paires critiques (texte principal sur fond principal, liens, boutons) te sauve de modifications “design” qui deviennent des régressions produit.

Erreurs fréquentes que je vois (et qui font mal plus tard)

La première, c’est de croire que “le DTCG” va résoudre le workflow à ta place. Non. Le DTCG t’aide à standardiser l’échange, mais il ne remplace pas tes règles d’équipe : naming, ownership, review, versioning.

La deuxième, c’est de mélanger les niveaux. Un token “sémantique” (ex : color.text.default) et un token “primitif” (ex : color.gray.900) n’ont pas le même cycle de vie. Si tu mets tout au même niveau, tu vas soit bloquer des évolutions, soit casser la sémantique. L’astuce simple : garde des primitives stables, et fais bouger la sémantique.

La troisième, c’est d’accepter des valeurs “hors système” parce que “ça dépanne”. Le 14px inventé dans un coin revient toujours te hanter. Si tu veux un pipeline fiable, il te faut une règle brutale : si ce n’est pas dans les tokens, ça doit faire lever un sourcil en review. Pas forcément interdire, mais au moins rendre visible.

Mon avis (assumé) : vise un pipeline boring, pas un export sexy

Les plugins d’export sont tentants parce qu’ils donnent une gratification immédiate. Mais en équipe, ce qui compte, c’est la chaîne “du changement design” jusqu’au “changement en prod”, avec une trace, une review, et une capacité à rollback. Ça, tu l’obtiens avec un pipeline boring : API → transformation déterministe → artefacts → version.

Et si tu n’as pas Enterprise, ce n’est pas une excuse. Tu n’auras peut-être pas la sync parfaite, mais tu peux déjà avoir l’essentiel : une extraction headless, du DTCG propre, et des garde-fous qui empêchent les surprises.

Conclusion : industrialiser les tokens, c’est surtout industrialiser les désaccords

Un pipeline tokens qui “marche”, ce n’est pas juste un script qui sort un JSON. C’est un workflow où les changements deviennent discutables, relisibles, versionnés, et testables. Tu ne vas pas éviter les débats design vs dev. Par contre tu peux éviter le pire : les débats sans preuve, et les régressions silencieuses.

La suite logique, si tu veux pousser le truc proprement, c’est de traiter les tokens comme un package interne à part entière (release automatique, changelog, consumers multiples) et de brancher la UI review (Storybook) directement sur les releases de tokens. Là, tu commences à avoir un design system qui se tient, même quand l’équipe change.

Sources

Cet article vous a plu ?

Commentaires

Laisser un commentaire

Entre 10 et 2000 caractères

Les commentaires sont modérés avant publication.

Aucun commentaire pour le moment.

Soyez le premier à donner votre avis !