Design System Builder
Ceci n'est pas une simple checklist de bonnes pratiques. Chaque règle ci-dessous existe parce que l'ignorer a produit un bug réel livré en production – la plupart silencieux (pas d'erreur, pas d'échec de build, juste un rendu incorrect dans le navigateur). references/bug-catalog.md contient l'incident spécifique derrière chacun d'eux. Lisez-le une fois avant de commencer un système suffisamment important pour avoir de l'importance.
Le fil conducteur : une règle qui ne vit que dans un commentaire ou un document sera inévitablement violée par accident, et la violation ne générera pas d'erreur. Chaque phase ci-dessous se termine en demandant "comment cela est-il appliqué automatiquement" – si la réponse est "quelqu'un s'en souvient", la phase n'est pas terminée.
Deux points d'entrée :
- Créer un nouveau → Phases 1 à 7 dans l'ordre.
- Auditer ou faire évoluer un système existant → Phase 8, qui renvoie aux phases 1 à 7 selon les besoins.
Phase 1 — Découverte
Demandez avant de construire ; deviner ici produit un système que personne ne voulait, et au moment où cela devient visible, il est coûteux de faire marche arrière.
- Utilisez des exemples concrets comme référence, pas des adjectifs. "Moderne et épuré" ne spécifie rien ; trois captures d'écran le font.
- La couleur porte-t-elle un sens, ou est-elle purement esthétique ? Si une palette a des valeurs catégorielles (élément, statut, marque), décidez en amont si chacune est aussi un comportement (mouvement, forme, poids) ou purement visuelle. Ajouter une signification à une couleur choisie pour son apparence est bien plus difficile que de décider cela en premier.
- Sur quoi repose le contenu ? Ne supposez pas qu'un fond de page et un fond de panneau sont "assez proches". Mesurez les combinaisons candidates avant de vous engager – voir Phase 2.3.
- Périmètre : nouvelle construction, affinement d'un système existant, ou comparaison avec un système antérieur/similaire ? Dirigez-vous vers la Phase 8 s'il ne s'agit pas d'une nouvelle construction propre.
- Distribution : un seul projet, ou réutilisé dans plusieurs ? Cette seule réponse décide si la Phase 6 (packaging) s'applique. Se tromper conduit soit à sur-ingénier un projet unique, soit à sous-ingénier quelque chose qui va dériver sur trois dépôts.
Phase 2 — Créer les tokens
Par ordre de priorité – se tromper ici fait que tout ce qui est construit au-dessus hérite d'une base erronée.
2.1 — Les couleurs sont créées une fois, sous forme de triplets bruts de canaux, jamais en hexadécimal nu.
--feu-rgb: 170 0 0; /* la seule valeur créée */
--feu: rgb(var(--feu-rgb)); /* dérivée */
--feu-doux: rgb(var(--feu-rgb) / 0.18); /* dérivée */
Ce n'est pas une question de style. Les modificateurs d'opacité de Tailwind (et de la plupart des systèmes d'utilitaires) (bg-fire/40) ne peuvent pas composer un canal alpha sur une valeur hexadécimale opaque var(--x) – la classe se résout silencieusement en rien, sans erreur ni avertissement. C'est exactement le bug qui a lancé le durcissement dont ce modèle est issu. Un nombre créé par couleur signifie également qu'il n'y a qu'un seul endroit où le modifier.
2.2 — Chaque couleur pouvant s'afficher en tant que texte obtient une variante -text, et les glyphes l'utilisent, jamais la couleur de base.
Vérifiez le contraste en calculant la luminance relative WCAG (formule dans assets/check-tokens.template.mjs), pas à l'œil. Une teinte qui atteint 4,5:1 contre une surface peut échouer contre une autre – c'est exactement pourquoi la 2.3 est importante.
2.3 — S'il y a plus d'un niveau de surface (page / panneau / encastré), définissez-le comme une véritable échelle d'élévation et mesurez la séparation entre les niveaux avant de vous engager. "Un peu plus clair" n'est pas une décision, c'est une supposition. Un vrai système a inversé un modèle d'élévation entier (encre-comme-page → vide-comme-page) après l'avoir livré, uniquement parce que quelqu'un a enfin calculé le contraste composite réel : 1,04:1 dans un sens, 1,19:1 dans l'autre. Faites ce calcul avant de construire sur cette base, pas après qu'un partie prenante dise "les cartes semblent plates".
Une fois l'échelle établie, chaque token sûr pour le texte doit voir son contraste vérifié contre chaque niveau sur lequel il peut apparaître, pas seulement celui pour lequel il a été conçu.
2.4 — Mouvement : une seule courbe d'accélération, un petit ensemble fixe de durées.
Si une animation JavaScript (framer-motion, GSAP, un requestAnimationFrame manuel) consomme ces valeurs, exportez la courbe et les durées sous forme de constantes JS en plus des valeurs CSS – une variable CSS est invisible pour JavaScript, et une bezier ou durée tapée à la main dérive immédiatement et invisiblement. Gardez les versions CSS et JS synchronisées (Phase 4).
2.5 — Les limites numériques pour tout ce qui ressemble à des lueurs/dégradés doivent être des nombres réels, vérifiés par rapport à la valeur réellement livrée – y compris les arrêts de dégradé, pas seulement les couleurs unies à un seul alpha. "Lueur subtile" sans plafond imposé est ainsi que la lueur ambiante pleine page revient, un composant à la fois, chacun ayant un aspect individuellement raisonnable.
2.6 — Chaque token reçoit un commentaire d'une ligne indiquant quand l'utiliser, pas seulement ce qu'il rend.
Phase 3 — Construire les primitives
- Les composants font exclusivement référence aux tokens. Pas de hexadécimal, pas de valeurs arbitraires, pas de littéraux
blanc/10– ce sont exactement ce que la garde de la Phase 4 vérifie, donc les écrire maintenant signifie simplement échouer à la vérification plus tard. - Toute utilisation d'un modificateur d'opacité (
bg-x/10) doit cibler un token qui est réellement alpha-composable selon 2.1 – vérifiable, donc vérifiez-le, ne l'évaluez pas à l'œil. - Ne codez pas en dur une variante dans les classes de base d'un composant si une prop est censée la remplacer.
className="shadow-lg"intégré dans chaque carte, avec une prop censée échangershadow-root-terre, produit deux utilitaires box-shadow avec une spécificité égale – le gagnant est déterminé par l'ordre source dans la feuille de style générée, silencieusement. Faites de la variante une table de correspondance indexée par la prop (Record<Variant, string>), jamais une classe de base plus un remplacement attendu. - Une prop de style n'est pas un substitut à un véritable élément interactif. Une carte qui semble cliquable (
cursor-pointer, soulèvement au survol) via une propinteractive, mais qui est un<div>, donne à un utilisateur de souris une affordance qu'un utilisateur de clavier n'obtient jamais – pas d'anneau de focus, pas de rôle, pas d'activation. Si quelque chose est destiné à être cliqué, affichez un<a>ou<button>(un jumeauCardLink/CardButton), pas un bouton de style sur la version passive. - Pour les composants interactifs vraiment complexes (modale, onglets, toast), appuyez-vous sur la plateforme. Le
<dialog>natif offre une gestion du focus et un Échap-pour-fermer gratuitement ; les versions faites à la main se trompent systématiquement sur l'un de ces points. Le tabindex rotatif pour les onglets est la chose la plus courante que les implémentations d'onglets faites à la main font mal (chaque onglet inactif reste un point d'arrêt de Tab) – vérifiez-le explicitement (les tabindex passent de[0,-1,-1,-1]à[-1,0,-1,-1]lors de la navigation par flèche), ne supposez pas. - Si le système a besoin d'actifs d'icônes/marques personnalisées, fixez les règles de dessin en amont (grille, épaisseur de trait, style de terminaison) et appliquez-les de la même manière que les règles de couleur – c'est aussi le travail de la Phase 4.
Phase 4 — Protéger
Écrivez un petit script de vérification, idéalement sans dépendances, ou avec un minimum de dépendances. C'est l'artefact à plus fort impact de tout le processus – chaque bug dans references/bug-catalog.md était une règle qui n'existait qu'en prose jusqu'à ce que quelqu'un l'encode ici.
Couverture minimale (adaptez assets/check-tokens.template.mjs) :
- Chaque token qu'un composant ou une présélection référence existe réellement.
- Pas de hexadécimal tapé à la main ni de couleur d'opacité littérale dans le code source du composant.
- Contraste WCAG pour chaque token sûr pour le texte contre chaque niveau de surface sur lequel il peut apparaître (2.3).
- Les limites numériques documentées sont appliquées en tant que nombres sur la valeur réellement livrée – y compris l'arrêt le plus lumineux d'un dégradé, pas seulement les couleurs simples (2.5).
- Les constantes de mouvement JS correspondent à leurs homologues CSS (2.4).
- Les règles structurelles faciles à violer par accident – par exemple, "pas de lueur de fond pleine page" : vérifiez qu'une utilité de lueur n'est jamais appliquée à un élément de niveau page, pas seulement que l'alpha du token est limité.
- Limitez toute règle qui pourrait correspondre à une prose ordinaire (par exemple une règle sur "une deuxième courbe d'accélération") aux contextes de code réels – attributs de classe, valeurs CSS – pas à du texte arbitraire. Un linter qui signale votre propre documentation sera désactivé, ce qui va à l'encontre du but.
La vérification doit en fin de compte s'exécuter sur les projets consommateurs, pas seulement sur le dépôt du système lui-même. C'est la leçon la plus importante ici : dans un vrai système, chaque violation de règle qui s'est réellement produite – onze tailles de police sous 24px, trois tailles de <h2> différentes sur un seul site, cinq valeurs d'espacement de lettres concurrentes, une deuxième courbe d'accélération, des durées inventées – s'est produite dans un projet consommateur du système, et la vérification interne n'a jamais regardé là-bas. Pendant longtemps, elle protégeait un code qui n'avait jamais enfreint une règle. Livrez le vérificateur pour qu'il puisse être exécuté contre n'importe quelle source (npx votre-check ./src), et donnez-lui une échappatoire limitée pour les exceptions réelles (une convention de commentaire votre-check-allow: <règle> – <raison>) plutôt que d'obliger les gens à désactiver toute la vérification.
Testez en négatif chaque vérification avant de lui faire confiance. Enfreignez délibérément chaque règle dans une copie d'essai et confirmez que le script échoue réellement. Une vérification qui n'a jamais été observée comme échouant est non vérifiée – vous ne savez pas si elle fonctionne ou si elle est silencieusement inerte.
Phase 5 — Documenter
Trois fichiers, répartis par public – résistez à l'envie d'en ajouter un quatrième. Un quatrième document (un "design.md" agrégé, un second guide de style en tant que document) est un second endroit pour que les mêmes valeurs dérivent ; trois qui possèdent chacun une question distincte (pourquoi / quoi faire / ce qui a été décidé) suffisent.
philosophy.md— le pourquoi, l'identité, le sens derrière les choix. Lisez-le une fois, rarement relu.foundations.md— les règles opérationnelles. Ce qu'une personne vérifie avant de construire quelque chose de nouveau. Devrait correspondre à peu près 1:1 avec ce que la garde de la Phase 4 applique – si une règle est dans l'un et pas l'autre, choisissez lequel manque et corrigez-le.decisions.md— un journal continu, entrée la plus récente en premier, des décisions de jugement spécifiques et leur raison. Ne supprimez jamais une entrée remplacée. Marquez-la comme remplacée et expliquez ce qui a changé et pourquoi. Inverser une décision réelle (un modèle d'élévation, une hiérarchie d'accentuation) sans trace écrite oblige la prochaine personne – souvent votre futur vous – à re-dériver le raisonnement à partir de zéro, ou pire, à casser silencieusement ce que la décision originale avait corrigé.- Une vitrine vivante qui affiche les tokens et composants réels – pas une description de ceux-ci. Si elle ne peut pas littéralement
importle package (un document HTML statique d'un seul fichier, par exemple), elle porte une deuxième copie intégrée de chaque valeur, et cette copie dérivera : la vitrine d'un vrai système est silencieusement tombée deux versions derrière ses propres tokens avant que quelqu'un ne la reconstruise comme quelque chose qui importe le package réel. Le correctif le plus rapide, si une vitrine statique est inévitable pendant un certain temps, est que la Phase 4 protège la copie intégrée par rapport à la source réelle – mais prévoyez de la retirer dès que le système est suffisamment stable pour avoir un véritable consommateur. - Si la vitrine peut être déployée, déployez-la sur une URL réelle et visualisez-la à une fenêtre réelle et étroite. Un bug de mise en page qui n'apparaît que lorsqu'une section spécifique est plus étroite que son contenu (un élément décoratif débordant intentionnellement élargissant tout le document, par exemple) est invisible pour toutes les vérifications statiques – pas une violation de token, pas un échec de contraste, pas une erreur de lint. Il n'existe que dans un navigateur réel à une largeur réelle. Un tel bug a été livré et a survécu à trois versions parce que le seul endroit où le système a jamais été visualisé était un fichier local.
- Écrivez une politique de versioning et l'ordre de lecture des documents, une fois qu'il y a plus d'un document. Coûte un paragraphe, empêche quelqu'un de re-dériver cela.
Phase 6 — Empaqueter et distribuer
Seulement si la Phase 1 a dit "réutilisé entre projets".
- Livrez en tant que package installable (
package.jsonavec une carteexports). Ne dites jamais "copiez ces fichiers dans votre projet" – la copie n'offre aucun chemin pour qu'un correctif en aval remonte en amont, ce qui fait que la source de vérité du système devient silencieusement la plus défectueuse des deux copies. C'était la cause racine du tout premier bug de toute cette lignée. - Si vous distribuez une présélection Tailwind : le tableau
contentd'une présélection n'est PAS fusionné par Tailwind. Le propre tableaucontentd'un consommateur remplace complètement celui de la présélection. C'est le deuxième bug le plus coûteux du catalogue, et le correctif "évident" – déclarer les globes dans la présélection et supposer qu'ils se combinent – ne fonctionne pas ; le build arrive octet pour octet identique, ce qui est l'indice. La présélection doit exporter ses globes, et chaque élément d'intégration (README, une commande de génération si elle existe) doit montrer aux consommateurs comment les déployer explicitement :content: [...preset.content, ...vosGlobes]. Vérifiez cela avec l'API de résolution de configuration du bundler, ne supposez pas – un artefact de build inchangé après un "correctif" est un signal de bug, pas une coïncidence. - Versionnez avec des tags, pas seulement des branches. Un pin de branche se déplace silencieusement sous un consommateur dès que la branche du dépôt source avance – c'est exactement ainsi qu'un consommateur peut finir par livrer une version non testée sans que personne ne le décide. Si un consommateur doit passer à un nouveau tag, rappelez-vous que les lockfiles peuvent épingler le commit exact résolu indépendamment de ce que le tag dans
package.jsondit – forcez la re-résolution et vérifiez quenode_modulesrapporte réellement la nouvelle version avant de faire confiance à une reconstruction. - Si le package offre une commande de génération/init pour les nouveaux consommateurs, faites-la émettre la configuration de contenu correcte et déployée dès le premier jour – ne comptez pas sur la discipline de lecture du README de chaque consommateur pour un piège suffisamment grave pour avoir sa propre entrée dans le journal des décisions.
Phase 7 — Vérifier
S'applique en continu, pas seulement une fois à la fin.
- Testez contre une nouvelle installation ou un clone, pas le répertoire de travail. Un répertoire de travail cache les exports manquants, les correctifs non commités et les caches obsolètes qu'un véritable consommateur rencontrera dès le premier jour.
- Un artefact de build inchangé après un "correctif" est un signal que le correctif n'a pas fonctionné, pas une coïncidence. Comparez la sortie réelle avant de croire que quoi que ce soit a changé.
- La vérification de type n'est pas une vérification. Le bug le plus grave de ce catalogue – une police s'affichant dans une famille de polices complètement erronée – a passé toutes les vérifications statiques disponibles et n'a été détecté qu'en rendant le composant dans un véritable consommateur et en le regardant. Rendez tout au moins une fois dans quelque chose de réel avant de le déclarer terminé.
- Méfiez-vous des API de plateforme qui ont l'air pratiques pour les revendications de vérification.
document.fonts.check()a l'air d'être l'outil parfait pour "cette police contient-elle réellement ce glyphe" et ment (elle rapporte av
(Le contenu a été tronqué dans l'original ; les phases restantes et leur contenu doivent être traduits de manière similaire si disponibles.)