Styles de composants
props.className restyle un composant. design.components restyle toutes les instances d'un type.
L'écart compte plus qu'il n'y paraît. Avant cette clé, une application qui voulait des coins carrés sur tous ses boutons répétait la même liste de classes à chaque point d'appel — et le mode de défaillance n'est pas un message d'erreur. C'est le point d'appel que quelqu'un a oublié, découvert plus tard par un client.
design:
components:
button:
parts:
root: rounded-none tracking-tightTous les boutons de l'application, en une ligne.
Sa place dans la cascade
Quatre couches, appliquées dans cet ordre — la dernière l'emporte en cas de conflit sur une même propriété :
- La recette Sovrium — le défaut livré pour le type.
design.components— cette clé. Votre réponse à l'échelle de l'application.props.className— l'instance unique. Elle bat votre propre règle applicative, ce qui rend une exception ponctuelle possible sans échappatoire.- Le socle — un petit ensemble non négociable, appliqué en dernier. Plus bas.
Indexé par type de composant, pas par vos noms
Les clés sont les types de composants du moteur — ceux que Sovrium dessine lui-même : button, table, dialog, input, et tous les autres.
Vos propres gabarits réutilisables, déclarés sous la clé components[] de premier niveau, ne se stylent pas ici. Vous les contrôlez déjà de bout en bout : leurs props, leurs enfants, leurs classes. Ce qu'ils portent ici, c'est plutôt leur guidage — ce à quoi chacun sert.
L'ensemble des clés est fermé. Une faute de frappe — buton: là où vous vouliez button: — est refusée au démarrage, en nommant la clé.
C'est délibéré, et c'est la raison pour laquelle ce n'est pas une table ouverte. Une table ouverte accepterait buton:, vous dirait que la configuration est valide, et servirait une application qui ignore toutes les classes écrites en dessous — sans que rien, nulle part, ne le signale.
Deux types sont exclus, chacun pour une raison :
| Exclu | Pourquoi |
|---|---|
customHTML |
C'est vous qui fournissez le balisage. Il n'y a aucun élément appartenant à Sovrium sur lequel poser une classe : l'entrée ne ferait rien. |
command-palette |
Il n'émet aucun balisage visible côté serveur — un bloc de configuration et son runtime, la surcouche étant construite dans le navigateur au premier ⌘K. Là encore, aucun élément moteur pour porter une classe. |
Quatre champs par type
Les quatre — parts, variants, states, replace — sont facultatifs, et chaque feuille est une liste de classes Tailwind. Au sein d'une même partie, elles se superposent dans l'ordre de déclaration : parts d'abord, puis l'entrée variants active, puis toute entrée states correspondante. Une classe d'état arrive donc après une classe de variante et l'emporte en cas de conflit sur une même propriété.
parts — les éléments dont le composant est fait
Tout composant a un root. Un composant composite nomme aussi ses éléments internes : une table a un header, une row, une cell.
design:
components:
table:
parts:
root: border-2
header: uppercase tracking-wide
cell: font-monoLes parties dont dispose un type sont définies par Sovrium — une partie est un élément que le moteur de rendu possède. Un nom qui n'en est pas une ne style rien.
Et, contrairement à une clé de type, il n'est pas refusé. Les noms de parties et de variantes forment un ensemble ouvert : headr: se décode proprement, démarre proprement, et ne peint rien. La clé de type peut être fermée parce que Sovrium connaît tous les types ; un nom de partie ne le peut pas, parce que les parties d'un type sont un détail d'implémentation qui bouge avec le moteur de rendu. Si un bloc semble ne rien faire, une partie mal orthographiée est la première chose à vérifier — et la console du système de design vous dira, partie par partie, de quelle couche vient chaque classe rendue.
variants — des classes pour une variante déclarée
design:
components:
button:
variants:
destructive:
root: border-2
ghost:
root: underline-offset-4Le vocabulaire des variantes dépend du type : un button en a sept, un divider aucune.
states — des classes pour un état d'interaction
Écrivez-les SANS le préfixe d'état. Sovrium l'ajoute.
design:
components:
button:
states:
hover:
root: bg-neutral-800
disabled:
root: opacity-40Écrire hover: { root: 'hover:bg-neutral-800' } est refusé, et pour une raison concrète plutôt que stylistique : cela produirait hover:hover:bg-neutral-800, qui ne génère aucun CSS. Vous auriez une configuration valide et aucun style au survol.
Les autres préfixes restent légaux et se combinent avec l'état : md:bg-neutral-800 sous hover devient hover:md:bg-neutral-800.
Les noms d'états forment un ensemble fermé, et chacun correspond à exactement un état CSS :
| Nom | S'applique quand |
|---|---|
hover |
le pointeur survole l'élément |
focus |
l'élément a le focus, par n'importe quel moyen |
focusVisible |
le focus vient du clavier — le chemin que protège l'anneau |
active |
l'élément est enfoncé |
disabled |
le contrôle est désactivé |
open |
une surcouche ou un panneau dépliant est ouvert |
selected |
l'élément est sélectionné |
checked |
le contrôle est coché |
invalid |
le champ a échoué à la validation |
focus et focusVisible figurent tous deux ici plutôt que d'être fondus en un seul, parce qu'ils diffèrent : focus se déclenche quand un script ou une souris déplace le focus ; focusVisible uniquement sur le chemin clavier.
Un état que Sovrium calcule plutôt que le navigateur — loading, pressed — n'est pas exprimable ici. Ceux-là n'ont aucun état CSS auquel se rattacher : c'est le moteur de rendu qui les décide. Les mélanger reviendrait à ce que la moitié de vos entrées s'appliquent par la feuille de style et l'autre moitié par du code, sans que vous puissiez dire, en lisant la configuration, de quelle sorte relève celle que vous venez d'écrire.
replace — abandonner la recette au lieu de s'y superposer
Par défaut, vos classes sont fusionnées par-dessus la recette Sovrium. p-8 l'emporte sur le p-4 de la recette, et tout ce que vous n'avez pas mentionné est hérité.
design:
components:
button:
replace: true
parts:
root: px-4 py-2 bg-slate-900 text-whitereplace: true est l'échappatoire pour un défaut qui n'est pas mauvais par degré mais par nature. Il abandonne la recette. Il n'abandonne pas le socle d'accessibilité.
Listes de classes légales, et celles qui ne le sont pas
Chaque feuille est validée au démarrage. Ce qui est refusé est étroit et délibéré : à l'intérieur d'une valeur arbitraire, les constructions qui laisseraient une liste de classes sortir de la feuille de style — url(, image-set(, attr(, expression( et @import.
Les valeurs arbitraires elles-mêmes restent légales (bg-[oklch(0.7_0.1_250)], text-(length:--sv-density-text)), la chaîne vide aussi. La règle n'est pas « pas de valeurs arbitraires » ; c'est « une liste de classes style, elle ne va rien chercher ». Une image relève de imagery, pas d'un arrière-plan détourné.
Ce que vous ne pouvez pas surcharger, et pourquoi
Une seule chose ici est un socle — des classes appliquées après les vôtres, pour que la garantie tienne quoi que vous écriviez. Le reste n'est pas constitué de classes du tout, et n'aurait de toute façon pas pu être changé depuis une liste de classes. Distinguer les deux compte, car seule la première est une promesse activement tenue par le moteur.
Le socle
L'anneau de focus. Un button, un input et un textarea conservent un anneau focus-visible visible ; un link conserve son soulignement focus-visible. ring-0 est une liste de classes parfaitement légale, et la plus destructrice que vous puissiez livrer — une personne qui navigue au clavier ne sait plus où elle se trouve — donc elle est acceptée, appliquée, puis surclassée.
Trois précisions, chacune bonne à connaître avant de compter dessus :
- Le socle couvre ces quatre types, et seulement la partie
root. Rien d'autre n'en porte. - Il est armé par votre déclaration. Une application qui ne déclare aucun bloc
design.componentspour un type n'a pas non plus de socle dessus. Le socle protège de votre surcharge ; là où il n'y a pas de surcharge, il n'y a rien à protéger. props.classNamel'emporte encore aujourd'hui. Le socle surclassedesign.components. Unfocus-visible:ring-0écrit sur une instance unique est une lacune connue, pas une garantie qui fonctionne comme prévu.
replace: true abandonne la recette. Il n'abandonne pas le socle.
Pas une liste de classes du tout
| Pas à vous | Pourquoi |
|---|---|
Les attributs et rôles aria-* |
Ce n'est pas du style. Une liste de classes n'est pas l'endroit où changer ce qu'un composant est. |
| Les noms des jetons de rôle | En renommer un casse toutes les recettes qui le lisent. Réaccorder sa valeur, c'est à quoi sert colorRoles. |
| Les parties dont chaque type est fait | Une partie est un élément que le moteur de rendu possède. |
| La marque élément Sovrium | Marque déposée. |
Donc : vous restylez, et sur les quatre types dotés d'un socle vous ne pouvez pas retirer l'indicateur de focus. Déclarer focus-visible:ring-0 sous design.components.button est accepté, appliqué — puis surclassé, tandis que toutes les autres classes du même bloc prennent effet normalement.
Voir quelle couche a gagné
GET /api/admin/design-system/provenance?type=button&part=root renvoie la liste de classes résolue et la chaîne qui l'a produite : une entrée par couche contributrice — default, app, floor — l'entrée du socle étant marquée locked et portant la raison de ce verrouillage. Une couche qui n'a rien apporté est omise, si bien que la longueur de la chaîne se lit toute seule. Le même détail est rendu sous forme de pastilles par partie dans la console du système de design.
Rampes de couleur et valeurs de rôles
Une palette n'est pas un sac de valeurs hexadécimales. C'est en général un petit nombre de rampes — des échelles de clarté ordonnées — plus un ensemble de rôles qui pointent dedans.
design:
ramps:
neutral:
'50': 'oklch(0.985 0 0)'
'500': 'oklch(0.56 0 0)'
'950': 'oklch(0.14 0 0)'
# Un échelon peut référencer l'échelon d'une autre rampe.
info:
'50': neutral-50
'500': neutral-500
colorRoles:
background:
value: neutral-50
dark: neutral-950
usage: Le fond de page. Jamais un fond de contrôle.Les échelons sont 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950. Ne déclarez que ceux dont vous vous servez.
La value d'un rôle dit ce qu'il vaut ; dark dit ce qu'il devient sous le schéma sombre, et l'omettre signifie « identique dans les deux ». Un rôle qui déclare une valeur définit le jeton : il n'a donc pas besoin d'exister déjà dans design.colors. Un rôle qui ne porte que de la prose documente, lui, un jeton qui y est déclaré.
Une référence qui ne résout rien est refusée au démarrage, avec la liste des rampes existantes — une référence non résolue arriverait au navigateur comme une variable que rien ne définit, et la surface peindrait sa valeur initiale en silence.
oklch() oui, var() non
Les valeurs de couleur acceptent l'hexadécimal, rgb(), hsl() et oklch(). Les rampes de Sovrium sont elles-mêmes écrites en oklch, et c'est le seul espace largement pris en charge dans lequel vous pouvez réaccorder une rampe par la clarté sans que la teinte dérive en dessous.
var() et color-mix() sont refusés. Ce sont des références, pas des valeurs : elles se résolvent dans la cascade du navigateur, donc rien de ce qui lit votre configuration ne peut savoir quelle couleur elles nomment — ni le vérificateur de contraste, ni l'export de jetons, ni un agent qui lit votre système de design. Quand vous voulez qu'un jeton en suive un autre, dites-le avec une référence que le schéma comprend : colorRoles[role].value.
Les échelles numériques
Deux clés, chacune produisant des utilitaires Tailwind :
design:
spacing: { '0': 0px, px: 1px, '0-5': 0.125rem, '4': 1rem }
motion:
durations: { fast: 120ms, base: 180ms }
easings: { default: 'cubic-bezier(0.2, 0, 0, 1)' }Le nom de chaque échelon devient le suffixe d'un utilitaire : --spacing-4 rend p-4 réel, --duration-fast rend duration-fast réel. Vous pouvez ajouter des noms que Sovrium ne livre pas — une durée page, un échelon d'espacement 96.
Un échelon d'espacement est une LONGUEUR en px ou rem. La clé acceptait auparavant n'importe quelle chaîne et écartait au moment de l'émission ce qui n'était pas une longueur : une configuration pouvait être valide et n'atteindre rien. Une mauvaise valeur est désormais refusée au décodage.
Les noms d'échelons s'écrivent en minuscules, chiffres et tirets. Un nom qui ne pourrait pas apparaître dans un nom de classe est refusé, et le message indique de quelle échelle il venait.
La typographie n'est pas une échelle libre : une taille, son interlignage, sa graisse et son approche se choisissent ensemble, sous un échelon nommé de design.typeScale.steps.
Pages liées
- Système de design — la clé
design, et le guidage qui vit sur vos gabarits - Échelle typographique — l'échelle de titres ordonnée
- Densité — l'échelle de ligne, contrôle, gouttière et petit texte
- Thème et couleurs — la palette d'auteur que porte
design.colors - Console du système de design — la galerie de composants, et les pastilles de provenance par partie
Dernière mise à jour 23 septembre 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.