Skip to main content
Voir en Markdown

Système de design

design est l'endroit où vit le système de design de votre application. On y trouve les jetons — les mêmes que theme a toujours portés — plus tout ce qu'un système de design doit transmettre et que des jetons seuls ne peuvent pas porter : les principes qui les justifient, la marque autour de laquelle il est construit, la voix de l'application, ce à quoi sert chaque couleur, l'échelle sur laquelle sa typographie est composée, le registre dans lequel ses images travaillent, et ce à quoi sert chaque composant.

C'est ce dernier point qui compte. Une palette de valeurs hexadécimales dit tout à un moteur de rendu et rien à un humain. primary: '#3b5bdb' ne précise pas s'il s'agit d'un fond de bouton d'action ou d'une couleur de texte courant : celui qui ajoutera une page plus tard — un collègue, ou un agent IA travaillant dans votre dépôt — choisit au jugé, et le design dérive. design rend ces règles déclarables, et sovrium design-system les transmet sous forme de fichier.

app.yaml
design:
  theme:
    colors:
      primary: '#3b5bdb'
      primary-fg: '#ffffff'

  principles:
    - La retenue plutôt que l'ornement
    - La couleur est dépensée sur l'erreur, et nulle part ailleurs

  logo:
    src: /logos/logotype.svg
    srcDark: /logos/logotype-clair.svg
    alt: Acme
    minWidth: 96px
    misuse:
      - Ne jamais recolorer la marque.

  typeScale:
    h1:
      size: '3rem'
      lineHeight: 1.1
      weight: 700
    body:
      size: '1rem'
      lineHeight: 1.6

  imagery:
    iconSet: Lucide
    photography:
      - Aucune image de banque d'images.

  voice:
    personality: [chaleureux, direct, jamais condescendant]
    pronoun: tu
    prefer:
      - "Commence chaque appel à l'action par son verbe : Déployer, Enregistrer, Supprimer."
      - Chaque état vide porte une ligne de guidage nommant la prochaine action.
    avoid:
      - Aucun point d'exclamation dans l'interface produit.
      - Aucun emoji.
    tone:
      empty: Dis ce que c'est, puis la seule prochaine action.
      loading: Dis combien de temps, et qu'on peut quitter la page.
      error: Énonce la contrainte, puis propose deux issues. Jamais d'accusation.
      success: Une ligne, terminée par un point. Pas de feu d'artifice.
      destructive: Nomme ce qui est supprimé, en quelle quantité, et si c'est réversible.

  colorRoles:
    primary:
      usage: Fond de bouton d'action principal uniquement. Jamais de texte courant, jamais une pastille de statut.
      pairsWith: primary-fg

  components:
    section-header:
      usage: Un bandeau titré qui introduit une section de page.
      when: À utiliser au-dessus de toute section comptant plus de trois enfants.
      dont: Ne jamais en imbriquer un dans un autre — les niveaux de titre entrent en collision.

design.theme et l'alias theme

design.theme est l'emplacement canonique des jetons de design. Il accepte exactement ce qu'accepte la clé theme de premier niveau, catégorie par catégorie et sans changement : colors, darkColors, fonts, spacing, shadows, borderRadius, breakpoints, animations, baseline, colorScheme, codeBlock.

La clé theme de premier niveau fonctionne toujours. C'est un alias pris en charge, et toutes les configurations existantes continuent de fonctionner sans modification.

app.yaml
# Canonique
design:
  theme:
    colors:
      primary: '#3b5bdb'

# Toujours pris en charge — un alias déprécié
theme:
  colors:
    primary: '#3b5bdb'

sovrium validate affiche un avis de dépréciation lorsqu'il rencontre la forme de premier niveau, sur la sortie d'erreur, et se termine tout de même avec le code 0. L'alias sera retiré à la prochaine version majeure.

Principes

design.principles est une liste ordonnée des convictions qui justifient vos jetons. Elle s'affiche en tête de l'export : celui qui le lit obtient le raisonnement avant les valeurs.

app.yaml
design:
  principles:
    - La retenue plutôt que l'ornement
    - Le visiteur est le héros, pas le produit

design.logo porte la marque et les règles pour la placer — la première section de toute charte de marque.

app.yaml
design:
  logo:
    src: /logos/logotype.svg
    srcDark: /logos/logotype-clair.svg
    alt: Acme
    clearSpace: Laisser une zone de protection égale à la hauteur de la marque sur les quatre côtés.
    minWidth: 96px
    misuse:
      - Ne jamais recolorer la marque.
      - Ne jamais l'étirer, la faire pivoter ni lui ajouter d'effets.
      - Ne jamais la poser sur une photographie chargée sans aplat de fond.
Champ Remarques
src Obligatoire. La marque principale, telle qu'elle apparaît sur la surface claire par défaut.
srcDark La variante affichée quand l'interface est en mode sombre. Voir l'avertissement ci-dessous.
alt Obligatoire. Le nom accessible — en général le seul nom de l'application.
clearSpace La zone de protection, en toutes lettres.
minWidth La plus petite largeur à laquelle la marque peut être reproduite : un nombre suivi de px ou rem.
misuse Ce qu'il ne faut jamais faire à la marque.

alt est obligatoire avec src parce qu'une marque sans nom accessible est un défaut, et non une déclaration partielle : elle parvient à un lecteur d'écran comme un pur néant. Écrivez le nom de l'application, pas logo : un lecteur d'écran annonce déjà l'élément comme une image.

clearSpace est délibérément du texte et non une dimension. Toute charte digne de ce nom exprime la zone de protection par rapport à la marque — « la hauteur du S sur les quatre côtés » — parce que c'est la règle qui survit à un changement de taille. minWidth est une dimension parce que, contrairement à la zone de protection, il s'agit réellement d'un seul nombre, et c'est le nombre qu'un consommateur peut faire respecter.

misuse est la moitié qui change quelque chose. « Utilisez le logotype » n'apprend rien à un designer qui allait le faire de toute façon ; « ne jamais le recolorer, ne jamais le poser sur une photographie chargée » est la phrase qui empêche ce que vous ne vouliez pas.

Où vit le fichier

src et srcDark portent une référence, pas des octets, et la seule chose qu'un consommateur puisse en faire est de la placer dans un attribut src=. Deux formes sont acceptées :

  • Chemin relatif à la racine/logos/logotype.svg. Cette forme couvre les deux emplacements de stockage avec une seule règle : un fichier du répertoire public de l'application est servi à /nom.ext, et un objet de bucket à /api/buckets/{bucket}/files/{chemin}.
  • URL absolue en https://https://cdn.example.com/logotype.svg, pour une marque servie par un CDN ou détenue par un tiers.

Tout le reste est refusé, chaque fois pour une raison :

Refusé Pourquoi
Un chemin relatif nu (logotype.svg) Il se résout par rapport à la page courante : la même déclaration charge /logotype.svg sur la page d'accueil et /fr/docs/guide/logotype.svg dans la zone documentation. Il n'existe aucune façon d'en écrire un correct : n'accepter que des erreurs serait donc le seul effet.
http:// Une application en https qui charge une image en http produit du contenu mixte : le navigateur le bloque en silence et la marque est simplement absente.
data: Insérer un logotype dans le HTML de chaque page se paie à chaque requête et ne se met en cache sur aucune.

Privilégiez le SVG. Un logotype est un dessin au trait : indépendant de la résolution, plus léger que n'importe quel encodage matriciel, net à toutes les densités, et sans aucune question de codec. La règle AVIF qui régit les images versionnées concerne les captures d'écran, pas les marques : le logotype d'un client est sa marque déposée, et le réencoder n'appartient pas à Sovrium. Une marque déclarée ici est servie telle quelle et n'entre jamais dans le pipeline de transformation d'images à l'exécution.

Voix

design.voice décrit la façon dont votre application s'exprime, quelle que soit la situation. Tous les champs sont optionnels.

Champ Type Contenu
personality string[] Les traits valables partout : ['chaleureux', 'direct', 'jamais condescendant'].
pronoun string La façon dont l'application s'adresse au lecteur — tu, vous, you, Sie.
prefer string[] Les tournures à privilégier, formulées comme des consignes.
avoid string[] Les tournures à refuser.
tone object La façon dont le registre change selon la situation — voir ci-dessous.

pronoun est une chaîne libre, et non une liste fermée, parce que le registre dépend de la langue : une application française tranche entre tu et vous, une application anglophone n'a pas ce choix à faire. Une liste fermée refuserait la première langue qu'elle n'énumère pas.

prefer et avoid sont les deux moitiés d'une charte rédactionnelle. Portez l'effort sur avoid : ce sont les refus qui arrêtent un texte hors marque mais plausible.

Ton

design.voice.tone couvre les cinq moments où un système prend la parole. Chaque valeur est une consigne qui dit comment écrire ce moment, pas la chaîne littérale : les chaînes dépendent de la locale et vivent dans languages.

Clé Le moment
empty Une table sans aucune ligne, une fonctionnalité jamais utilisée.
loading Une tâche longue pendant laquelle le lecteur attend.
error Tout ce qui arrête le lecteur : validation, réseau, refus.
success Un enregistrement confirmé, un déploiement terminé.
destructive Une confirmation qui doit nommer sa conséquence.

Déclarez les moments auxquels vous avez réfléchi et omettez les autres. Une clé absente vaut mieux qu'une consigne inventée pour remplir le tableau.

Rôles de couleur

design.colorRoles répond à la question à laquelle theme.colors ne peut pas répondre : à quoi sert cette couleur ?

app.yaml
design:
  theme:
    colors:
      primary: '#3b5bdb'
      primary-fg: '#ffffff'
  colorRoles:
    primary:
      usage: Fond de bouton d'action principal uniquement. Jamais de texte courant.
      pairsWith: primary-fg

usage dit à quoi elle sert et — plus utile encore — à quoi elle ne sert pas. pairsWith nomme le jeton avec lequel celui-ci est conçu pour se marier, en général son pendant de premier plan : le lecteur n'a alors pas à remesurer le contraste.

Chaque clé doit nommer une couleur déclarée dans votre palette. Un rôle documentant un jeton inexistant est un guidage sur lequel personne ne peut agir, et c'est en pratique une faute de frappe — sovrium validate le refuse donc et liste les jetons qui existent réellement. pairsWith n'est délibérément pas vérifié de la même façon : un pendant légitime est souvent un jeton de rôle de la plateforme que votre application n'a jamais redéclaré.

Échelle typographique

design.typeScale est l'échelle ordonnée sur laquelle votre typographie est composée — douze niveaux nommés, de display jusqu'à overline, chacun avec sa taille, son interlignage, sa graisse et son approche.

app.yaml
design:
  typeScale:
    h1:
      size: '3rem'
      lineHeight: 1.1
      weight: 700
      letterSpacing: '-0.02em'
    body:
      size: '1rem'
      lineHeight: 1.6

C'est la seule clé de design qui émet du CSS : chaque niveau déclaré devient une propriété personnalisée --text-{niveau} avec ses modificateurs, et un utilitaire Tailwind text-{niveau}. C'est ce qui en fait le remplaçant opérationnel des trois champs inertes de theme.fonts signalés en fin de page.

Voir Échelle typographique pour l'échelle complète, les règles d'unités, et ce que l'export en fait.

Imagerie

design.imagery couvre l'apparence de l'application là où les jetons n'atteignent rien. Deux pages peuvent utiliser exactement les mêmes jetons et ressembler malgré tout à deux produits différents, parce que l'une a choisi une photo de banque d'images montrant des gens qui pointent un tableau blanc et l'autre une capture du produit en fonctionnement.

app.yaml
design:
  imagery:
    principles:
      - Montrer le produit qui fonctionne, jamais une métaphore du produit.
      - Une personne dans une image fait son travail, elle ne pose pas.
    photography:
      - Aucune image de banque d'images.
      - Lumière naturelle uniquement — aucun étalonnage vers une teinte de marque.
      - Les captures d'écran sont prises en 2x sur un fond neutre.
    iconSet: Lucide
    patterns:
      - Une seule texture de grain de papier à 4 % d'opacité, jamais plus d'une surface par écran.
      - Les illustrations sont au trait d'épaisseur unique dans la couleur signature, jamais en aplat.
Champ Type Contenu
principles string[] Le registre dans lequel les images travaillent, sous forme de convictions. Le pendant de design.principles pour l'image.
photography string[] Les règles concrètes qu'une personne applique en choisissant ou en photographiant.
iconSet string Le nom de l'unique bibliothèque d'icônes dont toutes les icônes proviennent.
patterns string[] Les marques non photographiques : texture, style d'illustration, géométrie de fond.

principles et photography sont séparés parce qu'un principe est un étalon de jugement et qu'une règle est une contrainte à respecter. « Montrer le produit qui fonctionne » est un principe ; « aucune image de banque d'images, jamais » est une règle, et les confondre fait passer la règle pour négociable.

iconSet est le champ au plus fort effet de levier. La dérive des icônes ne vient pas de quelqu'un qui choisit une mauvaise icône : elle vient de trois auteurs qui choisissent chacun une icône raisonnable dans trois bibliothèques différentes, après quoi aucune discipline sur les jetons ne fera ressembler la barre d'outils à un seul produit. Nommer la bibliothèque une fois supprime la décision. C'est un nom, et non une URL ou un identifiant de paquet : ce dont le lecteur a besoin, c'est de savoir dans quelle bibliothèque chercher — une version épinglée répond à une autre question et se périme à chaque montée de version.

Rien dans imagery ne nomme de fichier, délibérément. Un logo est un artefact précis avec une URL ; l'imagerie est une classe d'artefacts assortie de règles, et les images elles-mêmes se déclarent là où elles servent : dans les pages, dans les enregistrements, dans les buckets. Une liste d'actifs ici aurait l'air de faire quelque chose et serait en réalité une seconde copie, non affichée, qui se périmerait dès que l'un des deux côtés changerait.

Guidage des composants

design.components donne à vos propres composants réutilisables autre chose que leur nom à présenter.

app.yaml
components:
  - name: section-header
    type: container
    children:
      - type: text
        element: h2
        content: $title

design:
  components:
    section-header:
      usage: Un bandeau titré qui introduit une section de page.
      when: À utiliser au-dessus de toute section comptant plus de trois enfants.
      dont: Ne jamais en imbriquer un dans un autre — les niveaux de titre entrent en collision.

Trois champs, trois questions différentes : usage dit ce que c'est, when la situation qui le désigne plutôt qu'un voisin, et dont le mésusage à refuser. Tous optionnels. Chaque clé doit nommer un components[].name déclaré — un guidage rattaché à un composant inexistant ne s'affiche nulle part, et la cause habituelle est un renommage appliqué d'un seul côté.

L'exporter

Tout l'intérêt de déclarer cela est de pouvoir le transmettre. Un seul générateur produit les trois surfaces : elles ne peuvent donc pas diverger sur ce qu'est votre système de design.

sovrium design-system

>_ terminal
# Le brief pour agent, sur la sortie standard
sovrium design-system app.yaml

# Versionné à côté de la configuration, référencé depuis vos consignes d'agent
sovrium design-system app.yaml --output DESIGN.md

# Le document de jetons, pour l'outillage
sovrium design-system app.yaml --format json --output tokens.json

Le markdown est le format par défaut, parce que le lecteur par défaut est un modèle. La commande s'exécute hors ligne — sans serveur, sans base de données — et convient donc à un hook de pré-commit ou à une étape d'intégration continue. Elle refuse un --format inconnu, et refuse une configuration qui échoue à la validation plutôt que d'exporter un système de design décrivant une application incapable de démarrer.

Les points d'accès

Les deux sont réservés aux administrateurs, en lecture seule, et renvoient 404 à toute autre personne.

Point d'accès Format
GET /api/admin/design-system.json Document W3C Design Tokens (DTCG) 2025.10.
GET /api/admin/design-system.md Le même brief que celui affiché par la commande.

La console du système de design restitue le même contenu sous forme de page lisible par une personne, et peut le publier derrière un lien révocable à destination de quelqu'un qui n'a pas de compte.

Dans le document JSON, les jetons apparaissent sous les types DTCG standard — les couleurs comme objets {colorSpace, components, hex}, les dimensions comme {value, unit} — l'échelle typographique comme jetons composites typography, et la couche propre à Sovrium (principes, logo, voix, rôles de couleur, imagerie, guidage des composants) voyage dans $extensions sous la clé com.sovrium.design-system, ce à quoi DTCG réserve précisément $extensions.

Ce que l'export laisse délibérément hors de l'arbre de jetons

Certaines valeurs n'ont pas de forme DTCG fidèle, et un jeton mal formé est pire qu'un jeton absent — un outil agirait dessus. Celles-ci conservent leur texte brut dans $extensions au lieu d'être forcées dans un type standard :

Valeur Raison
theme.shadows.* Les ombres DTCG sont décomposées en color/offsetX/offsetY/blur/spread ; une chaîne CSS box-shadow brute ne peut pas être reconvertie de façon fiable.
Une valeur d'espacement comme clamp(1rem, 2vw, 3rem) Une dimension DTCG, c'est un nombre et une unité.
theme.darkColors DTCG n'a pas de notion de mode ou de schéma dans cette version.

Trois champs de theme.fonts remplacés

Ceux-ci passent la validation, puis rien ne les lit. Ils sont signalés ici, et nommés dans l'export sous $extensions, pour que vous n'entreteniez pas une valeur sans effet :

Champ Ce qui se passe réellement
theme.fonts.*.lineHeight N'atteint rien. Aucune propriété CSS personnalisée n'est émise pour ce champ.
theme.fonts.*.size N'atteint que l'ancien moteur de rendu de section hero, sous forme de taille de police en ligne. Ce champ ne devient jamais une variable CSS.
theme.fonts.*.weights Seule la première entrée est lue, et uniquement par ce même moteur hero. Ce champ n'atteint jamais une règle @font-face : weights: [300, 400, 700] ne charge donc aucun fichier de police supplémentaire.

Déclarez plutôt les tailles, l'interlignage, la graisse et l'approche dans design.typeScale, où chaque niveau émet une véritable propriété personnalisée CSS et un utilitaire text-{niveau} utilisable. Pour charger plusieurs graisses d'une police, déclarez explicitement les variantes nécessaires dans theme.fonts.

sovrium validate affiche un avis Superseded: nommant chaque chemin déclaré, et se termine avec le code 0. Remplacés, et non dépréciés : « déprécié » signifie ceci fonctionnait et va disparaître, et ces trois champs n'ont jamais fonctionné. Ils continuent d'être décodés jusqu'à la prochaine version majeure, aux côtés de l'alias theme de premier niveau — les refuser maintenant arrêterait une application qui démarre sans rien changer au rendu. Aucune conversion n'est faite pour vous non plus : typeScale est par niveau là où theme.fonts est par police, si bien qu'une traduction automatique devrait deviner quelle police correspond à quel barreau.

Pages associées

Dernière mise à jour 28 août 2026

Cette documentation a été rédigée avec de l'IA : des erreurs ou du contenu obsolète sont donc possibles. Sovrium est en bêta. Les contributions et corrections sont les bienvenues.

Construit avec Sovrium