← Tous les projets

yesid.dev-design : une base visuelle commune

Une base visuelle commune, une place pour chaque produit

Un bouton, un titre et une petite réaction au clic peuvent donner un air de famille à plusieurs produits. Garder ces détails cohérents devient plus exigeant quand chaque produit a son contenu, son fonctionnement et son propre calendrier de mises à jour.

J'ai créé yesid.dev-design pour donner une base commune à mes projets, tout en gardant leurs différences explicites. Le projet rassemble les règles visuelles, les éléments d'interface et les comportements d'interaction de la marque yesid. Transit et yesid.dev en ont tous deux adopté des versions. Une galerie de composants permet de voir comment les éléments partagés fonctionnent ensemble.

C'est un projet d'infrastructure en évolution, avec son propre code, ses versions et ses responsabilités. Il vise à rendre les décisions communes plus faciles à maintenir et les changements plus faciles à examiner.

Le projet en bref#

Le problème#

Plusieurs produits doivent avoir un air de famille sans hériter des décisions propres aux autres. Maintenir les mêmes règles visuelles à plusieurs endroits rend plus difficile la distinction entre une différence voulue et des copies qui se sont éloignées.

Ce que j'ai construit#

Une base commune pour les valeurs de design, les commandes réutilisables et le mouvement, avec une galerie qui rend leur comportement visible. Elle comprend aussi des mécanismes de contrôle de qualité, de métadonnées de référencement, d'analytique et de routage multilingue. Chaque partie a une responsabilité définie et un parcours de livraison révisé.

Les produits concernés#

Transit et yesid.dev ont adopté cette base. Ils conservent leurs pages, leurs contenus, leurs données et leurs choix d'exécution. La galerie interne fournit des exemples neutres pour examiner les comportements partagés avant que chaque produit vérifie sa propre utilisation.

Le principal compromis#

Partager davantage de code relie aussi davantage de décisions. Transit exige des cartes plates; yesid.dev conserve un biseau et une ombre au survol. Je garde ces différences dans les produits, même si cela laisse une part de duplication. Un composant composé devient partagé seulement lorsque trois produits indépendants ont besoin du même comportement et des mêmes responsabilités.

Où en est le projet#

Le registre du 2 septembre 2026 indique que les deux produits ont adopté v0.13.2. Une version étiquetée v0.13.3 existe, et la branche principale examinée contient du développement ultérieur. Les produits choisissent leurs mises à jour de façon délibérée. Ces relevés décrivent l'adoption du code; ils ne prouvent pas la version actuellement servie par chaque page en ligne.

Mon rôle#

Je dirige les choix visuels, l'architecture, la répartition des responsabilités et l'acceptation des changements. L'IA aide à la réalisation. J'examine ses résultats à partir du code et des tests, et je reste responsable des décisions que j'accepte.

Pour aller plus loin#

Chaque section ci-dessous approfondit un aspect du projet avec les raisons des choix, des exemples concrets et les contrats techniques :

Origines · Valeurs de design · Comportement des composants · Limites entre produits · Versions et adoption · Rôle et état du projet

La base se trouvait d'abord dans yesid.dev. La première étape consistait à en extraire les éléments réutilisables sans refaire discrètement l'apparence du site en même temps. Si une couleur, un espacement ou une animation changeait pendant ce déplacement, il devenait plus difficile de distinguer une décision de design d'une erreur d'extraction.

Cette distinction a guidé la première version. La version v0.1.0 conserve un point de référence précis du site d'origine, avec les différences liées à l'extraction documentées. Parmi les exceptions consignées, on trouve des chemins d'importation réécrits et le maintien de certains mouvements plus spécialisés dans l'application. Les changements suivants reçoivent de nouveaux numéros de version. Le point de départ reste disponible et son étiquette de version ne bouge pas.

Le projet pouvait ainsi répondre à deux questions distinctes : l'extraction a-t-elle préservé la base prévue, et un changement de design ultérieur mérite-t-il d'être adopté? Déplacer le code et modifier l'expérience peuvent être utiles, mais chaque décision demande sa propre révision.

Les produits que la base sert#

Transit et yesid.dev ont besoin d'un air de famille reconnaissable tout en remplissant des rôles différents. Leur base commune comprend les couleurs, la typographie, les commandes d'interface et les mécanismes d'interaction. Chaque produit détermine encore la structure de ses pages, sa navigation, son contenu, ses données et son environnement d'exécution. La base ne décide ni du traitement des données de Transit ni des choix éditoriaux de yesid.dev.

Le dépôt contient aussi une galerie de composants privée. Elle utilise directement les paquets et rend leur comportement visible avec des exemples neutres. C'est un endroit utile pour examiner une commande avant de l'intégrer à un produit. Cette application d'intégration interne a un rôle différent de celui des deux produits externes et son propre cycle de versions.

Cette distinction explique la raison d'être du projet. Un bouton partagé peut offrir une apparence commune et un comportement prévisible. Il ne peut pas déterminer le sens d'une action dans un outil de transport ou le message qui convient à une page de projet. Je voulais donner une place claire aux décisions réutilisables, tout en gardant les décisions propres aux produits dans leur contexte.

La réalisation prend la forme d'un espace de travail Bun et Turborepo. Les paquets connexes sont maintenus ensemble et la galerie se trouve dans le répertoire des applications. Cette organisation permet de travailler sur les valeurs de design, les composants et leurs exemples dans un même dépôt. La distribution passe quand même par des versions délibérées : un changement qui fonctionne dans la galerie ne devient pas automatiquement une mise à jour des produits.

Le langage visuel de la marque s'inspire des infrastructures et de la signalisation. L'orange identifie les interactions. Le jaune sert aux repères et à l'orientation. Le blanc réfléchissant et le noir structurel donnent à la palette son contraste et son caractère. D'autres couleurs expriment des états comme la réussite ou l'erreur. L'intention est de créer un vocabulaire reconnaissable qui puisse servir plusieurs types d'information.

Ces choix sont conservés dans un ensemble structuré de valeurs de design, souvent appelées tokens. Un token donne un nom et un sens convenu à une couleur, une taille de texte, un espacement, un arrondi ou une durée d'animation. Les composants utilisent ces valeurs plutôt que de choisir chacun une variante presque identique. Le lien entre une décision de design et ses usages devient ainsi plus facile à examiner.

Une source, plusieurs résultats#

La source modifiable est un fichier JSON au format DTCG, une manière structurée de décrire les valeurs de design. Des générateurs transforment cette source dans les formats nécessaires aux feuilles de style, au code de mouvement et à la référence de design. Les fichiers générés sont comparés à cette source pour repérer les copies retouchées à la main qui pourraient devenir une version concurrente du design.

L'adaptateur de génération du dépôt indique précisément les quatre fichiers produits :

code ts
export const artifactPaths = [
  'DESIGN.md',
  'apps/gallery/src/app.css',
  'packages/motion/src/tokens.ts',
  'packages/tokens/tokens.css',
] as const;

Ces fichiers répondent à des besoins différents : la référence de design, les styles de la galerie, les valeurs de mouvement et la feuille de style commune. Le moteur de génération reçoit des données et retourne du contenu. Un adaptateur propre au dépôt décide où l'écrire. Chaque produit qui utilise le moteur conserve ses chemins de sortie et doit identifier correctement sa propre source dans les en-têtes des fichiers générés.

Cette séparation évite d'intégrer la structure des dossiers d'une application dans le paquet partagé. Elle laisse aussi au produit la possibilité de conserver des valeurs locales révisées ou une organisation de fichiers particulière, sans les présenter comme des choix universels de la marque.

Un petit changement dont on peut suivre le parcours#

Un test du dépôt donne un exemple concret. Dans une copie temporaire réservée au test, il fait passer la durée d'animation rapide de 150 à 151 millisecondes. Il s'attend à ce que le fichier TypeScript de mouvement et la feuille de style du paquet changent, tandis que celle de la galerie reste intacte. Il relance ensuite le générateur en mode vérification et s'attend à ce qu'il n'y ait plus de changement. La milliseconde ajoutée sert au test; ce n'est ni une refonte ni une amélioration mesurée.

L'exemple vérifie deux propriétés utiles. Un changement à la source doit atteindre les résultats qui en dépendent, et une génération répétée doit donner le même résultat. L'outil compare le contenu existant avant l'écriture, ce qui évite de réécrire les fichiers inchangés.

D'autres vérifications complètent ce parcours : les fichiers conservés dans le dépôt sont comparés au contenu généré, la commande de vérification continue des tokens détecte les différences, et un crochet local de commit facultatif refuse les fichiers générés préparés sans changement pertinent à la source. Les fins de ligne restent volontairement identiques entre systèmes d'exploitation, car une comparaison octet par octet détecte aussi ces différences invisibles. Ces mécanismes aident à repérer les écarts, chacun dans son périmètre.

Des valeurs fluides et un passage vers les outils de design#

Certaines tailles de texte et certains espacements sont fluides. Ils varient avec la largeur disponible entre un minimum et un maximum. La source conserve les trois parties du calcul, dont la valeur préférée, et le générateur CSS produit une expression clamp. On préserve ainsi la décision sous-jacente plutôt que la seule taille observée dans une capture.

Les formats de destination n'offrent pas tous les mêmes possibilités. L'export Figma transporte l'expression clamp sous forme de texte; il ne la transforme pas en formule responsive native. La typographie de la référence de design utilise le maximum structuré lorsque son format n'accepte qu'une dimension. Le passage aller-retour avec Figma reste aussi manuel : exporter les variables, les appliquer dans l'outil de design, puis vérifier l'export retourné. Le lien entre code et design existe, avec des limites de traduction explicites.

La galerie utilise le vrai code des composants avec du contenu de démonstration. Elle présente des boutons, des onglets, un sélecteur avec recherche, des sections repliables, des zones de défilement et d'autres commandes. Elle montre aussi les éléments propres à l'identité visuelle : repères de station, libellés de section, cadres inspirés des plans techniques et curseur de terminal.

L'état initial d'une commande répond seulement à une partie des questions. La galerie inclut les commandes désactivées, le chargement, les erreurs, le contenu qui déborde et les libellés fournis par le produit. Les thèmes clair et sombre permettent d'examiner les mêmes éléments dans des conditions visuelles différentes. Les scénarios dans le navigateur couvrent les interactions rendues, le maintien du contenu dans l'espace disponible et des comportements liés à l'accessibilité, en plus des comparaisons de captures.

La responsabilité d'un composant#

Ici, un contrat décrit ce qu’un composant doit afficher, comment il doit réagir et ce qui reste à décider par le produit qui l’utilise. Il comprend des détails qui ne sont pas toujours visibles dans la première capture.

Le sélecteur avec recherche en donne un bon exemple. Ses mécanismes partagés gèrent l'ouverture, la valeur sélectionnée, la recherche saisie et l'effacement. Le produit fournit les options, leurs valeurs stables, les libellés visibles, le texte de recherche et les textes d'accessibilité. Le contrat examiné précise aussi que la fermeture réinitialise la recherche temporaire et qu'une valeur changée de l'extérieur met à jour le libellé affiché. Ce sont des comportements observables sur lesquels un produit peut s'appuyer et qu'il peut tester.

Certaines exigences traversent la limite du paquet. Le sélecteur utilise une variable de taille minimale de cible tactile que le produit doit définir à 44 pixels. Le produit doit aussi fournir un libellé de fermeture traduit lorsqu'il utilise un panneau de type Sheet. Réutiliser une commande comporte donc des obligations de configuration; son importation ne suffit pas à établir que la page complète est utilisable.

Le même principe s'applique à la sémantique HTML. Une commande qui agit comme un lien et une autre qui agit comme un bouton exposent les attributs et la référence d'élément correspondant à ce qu'elles affichent réellement. L'interface de programmation reste ainsi alignée sur l'élément rendu, au lieu d'accepter des combinaisons que le composant ne peut pas respecter.

Le mouvement et sa réduction#

Le mouvement suit aussi des règles. Les effets plus importants ou continus tiennent compte de la préférence de réduction des animations, tandis que certaines réactions brèves à une interaction restent actives. La politique distingue le suivi du pointeur, les grands changements d'échelle, les mouvements continus et les effets de défilement de réactions plus petites, comme un changement de couleur ou une brève réponse à un appui.

Par exemple, à l’activation de l’effet, le léger déplacement d'une carte qui suit le pointeur reste désactivé si la réduction des animations est demandée ou si l'appareil ne prend pas en charge le survol. Ces trois vérifications viennent du comportement de déplacement de la carte :

code ts
	if (typeof window === 'undefined') return { destroy: () => {} };
	if (isPrefersReducedMotion()) return { destroy: () => {} };
	if (!window.matchMedia('(hover: hover)').matches) return { destroy: () => {} };

La première vérification permet aussi au comportement de ne rien faire dans un environnement sans navigateur. Si les conditions permettent l'effet, le code attache des écouteurs de pointeur et met à jour les décalages utilisés par les éléments à l'intérieur de la carte. Quitter la carte remet ces valeurs à zéro, et la destruction du comportement retire les écouteurs.

Le moment de la vérification fait partie du comportement. Cette lecture synchrone de la préférence se fait à l'initialisation. Pour cette politique, un changement du réglage du système d'exploitation prend effet à la prochaine initialisation ou navigation; chaque effet déjà attaché ne réagit donc pas nécessairement immédiatement. D'autres comportements ont leurs propres règles CSS, comme l'arrêt de la pulsation d'un squelette de chargement ou le retrait de la transition d'une section repliable.

Le contenu fermé demande aussi plus qu'une animation. Le contenu repliable partagé peut rester monté tout en devenant inerte et masqué aux technologies d'assistance lorsqu'il est fermé. Un produit qui remplace une enveloppe plus simple doit tenir compte de cette structure supplémentaire et du comportement à l'état fermé. La transition visuelle et la possibilité d'interagir avec le contenu sont deux choses à réviser.

Ce que les vérifications permettent d'établir#

Les tests des paquets examinent les contrats partagés. La galerie examine leur intégration dans une interface rendue. Les moteurs de contrôle examinent notamment le contraste, les motifs de style et la couverture des métadonnées de référencement, à partir de règles fournies par le produit. Celui-ci choisit sa palette, ses seuils, ses exclusions et ses résultats attendus; le moteur retourne les constats.

Une vérification en échec indique ainsi une responsabilité plus précise. Une interaction commune incorrecte relève du paquet. Une exigence visuelle propre au produit relève de son adaptation locale. Une capture de la galerie renseigne sur la galerie. L'accessibilité de la page complète et le comportement du produit doivent encore être examinés dans leur contexte réel.

Les décisions les plus importantes portent sur ce que chaque produit conserve. La réutilisation crée un lien de maintenance : quand plusieurs produits dépendent du même comportement, sa modification les oblige à en examiner les conséquences.

Le conflit entre les cartes#

Transit et yesid.dev partagent une base, mais leurs cartes n'ont pas à être identiques. Le contrat documenté de Transit exige une carte plate, sans ombre ni bordure en relief. yesid.dev conserve un biseau et une ombre au survol. Ces exigences sont explicites, incompatibles et appuyées par des tests propres aux produits.

La surface commune peut rester partagée pendant qu'un composant d'adaptation ou un style local prend en charge la différence. Le paquet partagé ne vérifie pas le nom de l'application pour choisir l'apparence à afficher. Si une abstraction proposée exige cette connaissance, les règles du projet renvoient ce comportement dans le produit.

La conséquence dépasse une préférence de style dans le code. Ajouter le biseau à la carte commune pour répondre au besoin d'un site ferait recevoir à Transit un changement que ses propres tests refusent. Si le paquet accumulait des exceptions nommées pour chaque application, sa maintenance demanderait de connaître toujours plus de règles propres aux produits.

Ce que couvre vraiment la règle de trois#

Les composants composés suivent une règle de trois : trois consommateurs indépendants, c'est-à-dire trois produits qui utilisent le composant, doivent avoir besoin du même contrat avant sa promotion dans un paquet partagé, avec une version délibérée. C'est une règle de gouvernance du projet. Elle ne compte pas les clients, ne fixe pas une cible de déploiement et ne signifie pas que chaque commande de base possède déjà trois usages en production.

CollapsibleSection illustre la distinction. Deux produits peuvent afficher un titre, un chevron et du contenu masqué tout en différant dans la composition de l'en-tête, la mémorisation de l'état, les signaux de contrôle et le comportement des animations. Les commandes repliables de base peuvent être partagées. Le composant qui gère toute la section reste local jusqu'à ce qu'un contrat commun soit établi par les usages.

La composition du pied de page suit le même raisonnement. De petits éléments partagés peuvent aider à le construire, tandis que sa navigation, son état, ses mentions et son organisation restent des décisions du produit. Une apparence familière ne détermine pas à elle seule qui doit posséder toute la composition.

Une partie de la duplication demeure donc volontaire. Elle coûte l'entretien des éléments locaux. En échange, les différences restent explicites tant que les besoins diffèrent. La mise en commun devient une décision appuyée par l'utilisation, plutôt qu'une hypothèse fondée sur deux fichiers qui se ressemblent.

L'état, la configuration et le contrôle du produit#

Une vue contrôlée peut afficher un état et offrir des fonctions de rappel sans décider où cet état est conservé ni ce que l'action signifie. QuietModeButton, par exemple, partage la structure de deux boutons, les icônes et les dimensions d'interaction. Le produit fournit les textes, l'état courant, la persistance et les deux actions. Réutiliser l'apparence ne transfère pas le contrôle du produit au paquet.

La configuration a elle aussi une limite. Le vocabulaire utilisé pour fusionner les classes de l'interface est fixé au démarrage de l'application, pour chaque ensemble de modules chargé. Une initialisation équivalente peut se répéter; une configuration contradictoire provoque une erreur. Elle ne doit pas dépendre d'une requête, d'un utilisateur, d'une organisation utilisatrice ou de la langue courante. Les modules du navigateur et ceux du rendu serveur ont leur propre initialisation. Une configuration commune ne devient ainsi pas un endroit où stocker un état variable propre à une personne.

Des domaines au-delà de l'interface visible#

La base comprend maintenant des mécanismes qui dépassent les styles. Le paquet de référencement construit des métadonnées structurées et des entrées de plan de site, tandis que le produit possède le contenu, les adresses canoniques, les choix d'indexation et les modèles d'images. Le routage multilingue partage l'analyse et la localisation des chemins; chaque produit choisit ses langues, ses pages publiées, ses règles de repli et son intégration au cadre applicatif.

L'analytique suit une limite semblable. Le paquet partagé fournit des mécanismes d'état et d'envoi tenant compte du consentement. Le produit fournit son domaine, son catalogue d'événements, les textes de consentement, ses choix de stockage et le transport. Dans le code examiné, le consentement et le domaine sont vérifiés avant le chargement du transport, puis de nouveau avant l'envoi. L'utilisation de ces mécanismes ne détermine pas à elle seule la politique de confidentialité du produit.

Ces domaines ont une raison commune d'être ici : leurs mécanismes répétables peuvent servir plusieurs produits. Chacun garde une responsabilité limitée. La navigation, les contrats de données, les identifiants secrets, les adaptateurs de déploiement et les décisions propres au produit restent à l'extérieur de la base.

Chaque produit adopte une version exacte. Une nouvelle version de la base ne modifie pas automatiquement tous les sites. Le changement se fait par une adoption révisée dans le produit qui la reçoit, avec ses propres fichiers générés et vérifications d'acceptation.

Des versions coordonnées et un cycle distinct pour l'outillage#

Sept paquets évoluent ensemble : les tokens, le mouvement, les contrôles de qualité, l'interface, les mécanismes de référencement, l'analytique et le routage multilingue. Cette coordination donne à chaque version un ensemble connu de paquets. Elle signifie aussi qu'un changement limité à une partie doit être examiné dans le cadre de cette version commune. La galerie privée reste à l'extérieur de ce groupe, et les réglages partagés de compilation et de tâches ont leur propre cycle de versions.

Les paquets distribuent du code source TypeScript et Svelte. Les produits importent les familles de composants nécessaires et activent explicitement les feuilles de style communes. Les obligations de compilation deviennent visibles : un produit utilisant Tailwind doit analyser le code source installé de l'interface, et sa feuille de style doit définir les valeurs d'application attendues par les composants.

Les rapports d'interface publique rendent les changements à l'API examinables. Chaque changement s'accompagne d'une note qui décrit son effet visible pour le produit et la migration éventuelle. La politique de compatibilité distingue aussi l'ajout d'une capacité, la correction d'un défaut et le retrait d'un contrat établi. Le retrait d'un contrat stable exige à la fois un délai documenté d'au moins 90 jours et une version mineure intermédiaire. Ces règles sont maintenues; elles ne dispensent pas de réviser une mise à jour.

Savoir ce qui a été installé#

L'outil d'adoption installe l'archive d'une version exacte et conserve un relevé de ce qu'il a reçu dans un manifeste. Ce relevé identifie l'étiquette de version, le commit associé, la taille et l'empreinte de l'archive, les paquets inclus, l'identité de l'outil d'adoption et l'empreinte des fichiers installés. Il relie les fichiers à une version précise et fournit une référence concrète aux vérifications suivantes.

Le mode de vérification hors ligne compare les fichiers installés avec leur relevé. Il ne contacte pas le service de publication pour prouver le résultat des vérifications en amont et n'établit pas que le produit fonctionne correctement. La provenance d'une version, l'intégrité locale et l'acceptation du produit répondent à des questions différentes.

La règle de circulation à sens unique découle de cette structure. Les produits ne corrigent pas le code partagé en retouchant leur copie installée. Un défaut commun est corrigé dans la base et livré dans une nouvelle version. Une différence propre au produit appartient à son adaptation locale. Autrement, la version indiquée dans le manifeste cesserait d'expliquer le code réellement exécuté.

Suivre un changement jusque dans le produit#

Prenons un changement révisé à une durée d'animation commune. La valeur source est modifiée en amont, les fichiers générés sont examinés et les vérifications concernées du paquet et de la galerie sont exécutées. La version décrit ce changement. Transit ou yesid.dev peut ensuite choisir cette version exacte, régénérer ses propres fichiers et examiner les endroits où la durée intervient dans son interface.

Un changement de composant pose d'autres questions. La nouvelle structure HTML a-t-elle modifié un sélecteur du produit? Une carte locale conserve-t-elle la surface prévue? Le contenu fermé se comporte-t-il encore correctement? Les libellés traduits tiennent-ils dans l'espace? Le contrat d'adoption demande les tests pertinents du produit, les vérifications de types, une compilation et une comparaison dans le navigateur selon les thèmes, formats et états d'interaction touchés.

Cette démarche ajoute du travail à une livraison. Elle rend aussi la décision traçable : ce qui a changé en amont, le produit qui l'a accepté et ce qui y a été vérifié. Une exécution réussie dans la galerie ne remplace pas ces réponses, car elle utilise les paquets de l'espace de travail et sa propre composition de démonstration.

Le retour en arrière suit le même parcours délibéré. Le produit adopte une version exacte déjà acceptée, ajuste ses dépendances à l'ensemble de paquets de cette version, régénère ses fichiers et répète ses vérifications. Les anciennes versions peuvent contenir moins de paquets. Restaurer les fichiers sans examiner ces dépendances laisserait une partie de la décision de mise à jour en suspens.

Mon rôle comprend la direction visuelle, l'architecture, la délimitation des responsabilités et les décisions d'acceptation. La réalisation est assistée par l'IA. Le fonctionnement documenté prévoit de réviser les résultats des agents, de remettre les hypothèses en question et de vérifier les affirmations dans le code et les tests. Je reste responsable de ce que j'accepte.

Le registre documente l'adoption de v0.13.2 par Transit et yesid.dev au 2 septembre 2026. La base possède aussi un tag de version v0.13.3. Des renforcements ultérieurs sont présents sur la branche principale, séparément de cette version étiquetée. L'explication technique présentée ici porte sur le code examiné et ses contrats documentés. Un relevé d'adoption indique le code reçu par un produit; ses pages en ligne demandent encore leur propre vérification.

Le projet a fait de la responsabilité une partie concrète du design. Une couleur a une source. Un composant a un rôle. Une différence entre produits a une place explicite. Une version a une identité, et son acceptation demande du travail dans le produit qui la reçoit.

Pour une personne qui envisage de me confier un projet, c'est l'aspect utile de cette étude de cas : ma façon d'aborder une interface qui doit continuer à évoluer après sa première mise en ligne. Les détails visuels comptent, tout comme la possibilité de comprendre la prochaine modification. Je continue à développer cette pratique à travers les décisions, les vérifications et les compromis que cette base rend visibles.

Une base commune pour les valeurs de design, les composants et le mouvement. Comment Transit et yesid.dev l’adoptent en gardant leurs responsabilités.

TypeScript
Dépôt privé (pour le moment)

Bâtissons quelque chosequi avance.

Base de données, pipeline, tableau de bord, site web : peu importe où ça accroche, c'est là qu'on commence.