Beaket Blog
Changelog

Le Design System qui dit non

Le Design System qui dit non

« La carte devrait avoir un border-radius de 4px ou de 8px ? »

C’était un fil de discussion de 47 minutes. Quatre ingénieurs, deux designers, un fichier Figma avec six variantes. La conversation couvrait les conventions iOS, les guidelines Material Design, l’alignement optique, et à un moment donné, le nombre d’or. Personne n’avait tort. C’était bien ça le problème.

On était déjà passés par là. Pas seulement pour le border-radius — pour les directions de dégradé, les valeurs de flou des ombres, les niveaux d’opacité des états désactivés. Chaque décision était mineure. Chaque débat était raisonnable. Et collectivement, ils dévoraient notre vélocité.

Alors on a arrêté de chercher la bonne réponse. On a supprimé la question.

Le coût des options

Chaque propriété visuelle d’un design system est une surface de décision. Le border-radius à lui seul génère des questions : les boutons et les cartes doivent-ils utiliser la même valeur ? Et les éléments imbriqués — le rayon intérieur doit-il soustraire le rayon extérieur ? Le rayon change-t-il selon la taille ?

Aucune de ces questions n’est difficile. C’est justement ce qui les rend dangereuses. Elles sont suffisamment simples pour que tout le monde ait un avis, mais suffisamment anodines pour qu’aucun avis ne soit manifestement meilleur. Du bikeshedding classique.

Nous avons manuellement tagué les commentaires de PR pendant un trimestre. Environ 30 % de nos fils de revue du design system portaient sur des valeurs cosmétiques sans impact sur l’utilisabilité. Pas la mise en page. Pas l’accessibilité. Pas les patterns d’interaction. Juste… l’esthétique.

C’est à ce moment-là que nous avons écrit les règles.

Pourquoi éliminer plutôt que contraindre

L’alternative évidente est de conserver les propriétés mais de les limiter. La plupart des design systems matures font cela — trois valeurs de border-radius autorisées, une échelle d’élévation des ombres, une palette de dégradés sélectionnée. Nous y avons réfléchi.

Le problème, c’est que les options contraintes génèrent quand même des débats. « Cette carte devrait utiliser radius-sm ou radius-md ? » est la même conversation avec moins de réponses. Moins, c’est mieux que beaucoup, mais zéro, c’est mieux que moins. Contraindre une propriété réduit les décisions. L’éliminer supprime entièrement la décision.

Nous avons aussi envisagé le linting — une règle ESLint qui rejette les valeurs non autorisées. Mais un linter qui impose « utilisez uniquement 0, 4 ou 8px de rayon » laisse encore trois choix. L’objectif n’était pas de faciliter la décision. C’était de la rendre inutile.

Quatre règles

La bibliothèque de composants de Beaket repose sur quatre contraintes visuelles :

  1. Pas de border-radius. Des angles vifs partout. Boutons, cartes, champs de saisie, boîtes de dialogue, info-bulles — tout est rectangulaire.
  2. Pas de dégradés. Couleurs plates uniquement. Chaque surface est une seule variable CSS.
  3. Pas d’ombres floues. Pas de box-shadow avec rayon de flou. Uniquement des ombres décalées.
  4. Pas d’opacité décorative. Les états désactivés utilisent des changements de couleur, pas de transparence. Pas de raccourci opacity: 0.5.

Ce ne sont pas des recommandations. Ce sont des règles strictes, appliquées en revue de code et documentées dans nos guidelines de composants. Une PR qui ajoute rounded-md est rejetée.

Pourquoi ces quatre propriétés en particulier ? Parce que c’étaient celles qui généraient le plus de débats avec le moins d’impact sur l’utilisabilité. L’espacement et la typographie affectent la lisibilité. La couleur affecte le sens. Mais la différence entre un border-radius de 4px et de 8px ? Entre une ombre floue de 4px et une de 6px ? Nous n’avons pas trouvé un seul cas où ces choix affectaient de manière mesurable l’utilisation du produit.

Après l’adoption des règles, nos PR du design system ont commencé à avancer plus vite. Les fils de revue se sont raccourcis. Les designers ont arrêté de produire plusieurs variantes du même composant ne différant que par le traitement des coins ou la profondeur des ombres. Les débats n’ont pas diminué — ils ont disparu.

Ce que nous utilisons à la place

Supprimer des options ne signifie pas supprimer la hiérarchie visuelle. Il faut toujours communiquer la profondeur, l’état et l’interaction. Voici comment nous avons remplacé chaque propriété.

Des ombres décalées au lieu du flou

Au lieu de box-shadow: 0 4px 6px rgba(0,0,0,0.1), nous utilisons des ombres décalées définies comme variables CSS :

--shadow-offset: 1px 1px 0px 0px var(--color-chrome);
--shadow-offset-hover: 2px 2px 0px 0px var(--color-chrome);
--shadow-offset-active: 0px 0px 0px 0px var(--color-chrome);

Trois états d’interaction. Zéro flou. C’est l’intégralité du système d’ombres.

L’état par défaut a un décalage de 1px — l’élément repose légèrement au-dessus de la surface. Le survol le pousse à 2px — un léger soulèvement. L’état actif le ramène à 0px — l’élément s’aplatit. C’est une petite animation mécanique qui communique clairement l’interaction et ne laisse rien à configurer au développeur.

En pratique, chaque composant interactif applique les trois mêmes tokens. Voici la base de notre composant bouton (simplifiée à partir de la chaîne CVA complète, qui inclut aussi les états désactivé et focus) :

const buttonVariants = cva(
  [
    "inline-flex items-center justify-center gap-2",
    "font-medium cursor-pointer",
    "shadow-offset hover:shadow-offset-hover active:shadow-offset-active",
    "transition-shadow duration-100",
    // ... disabled, focus-visible, and icon styles
  ].join(" "),
  // ... variants
);

Plus de décisions sur le niveau d’ombre qu’un composant « mérite ».

Des couleurs plates via les variables CSS

Notre palette de couleurs — nous l’appelons « Porcelain » — comporte 14 neutres allant de graphite (#030509) à paper (#ffffff). Elles sont froides et teintées de bleu. La description du thème dans notre documentation dit : « Précision industrielle. Gravé sur des panneaux de contrôle en laque synthétique noire. Blanc pur, encre bleu-noir froide, ombres spectrales, accent sarcelle. »

Chaque référence de couleur passe par un token sémantique :

--color-text-primary: var(--color-graphite);
--color-text-secondary: var(--color-steel);
--color-bg-primary: var(--color-paper);
--color-border-default: var(--color-chrome);

Nous évitons les couleurs par défaut de Tailwind comme bg-white ou text-gray-500. Chaque couleur est une variable nommée, et chaque variable correspond à un rôle. Personne ne débat pour savoir si quelque chose devrait être gray-100 ou gray-200 — le token sémantique répond déjà à la question.

Des angles vifs partout

Celui-ci ne nécessite aucun remplacement. On ne met simplement pas de border-radius.

Ce qui nous a surpris, c’est à quel point les angles vifs interagissent bien avec les ombres décalées. Une carte arrondie avec une ombre décalée a l’air bizarre — le bord dur de l’ombre entre en conflit avec le coin doux. Mais une carte aux angles vifs avec une ombre décalée a l’air intentionnel. La géométrie est cohérente. Les contraintes se renforcent mutuellement d’une manière que nous n’avions pas pleinement anticipée.

Le langage visuel devient cohérent par soustraction. Quand tout est anguleux, rien n’a besoin de justifier sa forme.

Là où les contraintes se cumulent

Le bénéfice le plus net de cette approche apparaît dans les préoccupations transversales — les patterns qui touchent chaque composant.

Les états désactivés sont notoirement incohérents d’une bibliothèque de composants à l’autre. Certaines utilisent l’opacité, d’autres changent le fond, d’autres font les deux. Nous avons un seul pattern, appliqué à chaque composant :

disabled:shadow-none disabled:cursor-not-allowed disabled:border-dashed disabled:border-chrome disabled:bg-frost disabled:text-steel

Boutons, champs de saisie, sélecteurs, cases à cocher, interrupteurs — tous identiques. Ombre retirée (l’affordance interactive disparaît), bordure en pointillés, fond frost, texte steel. Quand vous avez vu un composant désactivé, vous les avez tous vus. Les contraintes ont supprimé la tentation d’adapter les états désactivés à la « personnalité visuelle » de chaque composant. Quand les composants n’ont pas de personnalité visuelle, les états désactivés convergent naturellement.

Les anneaux de focus fonctionnent de la même manière. Chaque composant focalisable utilise :

focus-visible:outline-2 focus-visible:outline-signal-blue focus-visible:outline-offset-2

Une couleur. Une largeur. Un décalage. Un utilisateur naviguant au clavier dans un formulaire voit exactement le même anneau sur chaque élément. Aucun composant ne le remplace. Aucune variante ne le supprime. Combiné avec le pattern uniforme de l’état désactivé, un utilisateur sait toujours exactement à quoi ressemblent « inactif » et « focalisé » — quel que soit le composant avec lequel il interagit.

La seule exception

Les boutons radio utilisent rounded-full.

C’est tout. C’est la seule exception dans toute la bibliothèque de composants. Elle est documentée dans nos guidelines de composants aux côtés des règles qu’elle enfreint, car même les exceptions doivent être des décisions, pas des accidents.

Un bouton radio carré perturberait les utilisateurs. La forme circulaire est si profondément ancrée dans les conventions d’interface que la supprimer sacrifierait l’utilisabilité au profit de la pureté esthétique. Nous avons jugé que c’était un mauvais compromis.

Vous pourriez demander : si l’utilisabilité justifie une exception pour les radios, pourquoi pas pour les avatars, les tags ou les interrupteurs ? Nous avons testé. Les avatars carrés avec des ombres décalées ressemblent à des cartes de profil — ils sont perçus comme intentionnels. Les tags et les interrupteurs sont moins liés à des conventions que les boutons radio. L’affordance circulaire est uniquement forte pour les radios ; rien d’autre n’a franchi le seuil.

L’objectif n’a jamais été « le brutalisme pour le brutalisme ». C’était de supprimer les décisions visuelles qui ne justifient pas leur complexité. Un bouton radio arrondi mérite sa forme. Une carte arrondie, non.

Ce que ça coûte réellement

Nous serions malhonnêtes si nous disions que cette approche n’a aucun inconvénient. Elle en a.

« Ça a l’air inachevé. » C’est une réaction réelle lors de tests utilisateurs. Les angles vifs et les couleurs plates ne transmettent pas le même niveau de finition que les interfaces arrondies et ombrées. Nous avons dû expliquer le raisonnement plus d’une fois.

Certains composants semblent rigides. Les info-bulles et les popovers, en particulier, paraissent plus lourds avec des angles vifs. Les ombres floues existent pour une raison — elles créent une sensation de flottement que les ombres décalées ne reproduisent pas tout à fait.

Les composants tiers cassent le pattern. Sélecteurs de date, éditeurs de texte riche, bibliothèques de graphiques — ils arrivent tous avec des border-radius et des ombres floues intégrés. Nous surchargeons ce que nous pouvons et acceptons une certaine incohérence aux marges. C’est une tension permanente, pas un problème résolu.

L’expression des designers est contrainte. Si vous êtes le genre de designer qui trouve du plaisir à créer des micro-interactions avec des superpositions de dégradés et une profondeur subtile, ce système vous semblera limitant. C’est le but — mais c’est quand même un coût réel en termes de satisfaction créative.

C’est polarisant. Certains ingénieurs adorent la simplicité. D’autres trouvent ça dogmatique. Nous n’avons pas entièrement résolu cette tension. Nous avons simplement décidé que le bénéfice en cohérence l’emporte sur le coût esthétique pour notre produit.

Ce que nous ne savons pas encore

Nous n’avons pas testé ce système à une véritable échelle — des centaines de composants, des interfaces denses en données, des intégrations tierces qui doivent avoir un rendu natif. Les tableaux, les graphiques et les tableaux de bord pourraient nécessiter des indices de profondeur que les ombres décalées ne peuvent pas fournir.

Nous ne savons pas vraiment si les utilisateurs développent la même compréhension intuitive de nos états d’ombre (défaut → survol → actif) que celle qu’ils ont avec les systèmes d’élévation conventionnels. Nous n’avons pas observé de régressions d’utilisabilité, mais nous n’avons pas mené de tests A/B formels sur ce point précis. Ça fonctionne pour l’instant. Mais « pour l’instant » est un petit échantillon.

Les contraintes comme décisions

Chaque contrainte dans ce système est une décision prise une fois pour ne plus jamais avoir à être prise. Pas de border-radius n’est pas une préférence esthétique — c’est un ADR (Architecture Decision Record) pour toute la bibliothèque de composants.

Si votre équipe passe du temps à débattre de propriétés visuelles qui n’affectent pas l’utilisabilité, envisagez de supprimer entièrement l’option. Ça pourrait vous manquer moins que vous ne le pensez.

Share