
# 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.

```yaml
design:
  components:
    button:
      parts:
        root: rounded-none tracking-tight
```

Tous 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é :

1. **La recette Sovrium** — le défaut livré pour le type.
2. **`design.components`** — cette clé. Votre réponse à l'échelle de l'application.
3. **`props.className`** — l'instance unique. Elle bat votre propre règle applicative, ce qui rend une exception ponctuelle possible sans échappatoire.
4. **Le socle** — un petit ensemble non négociable, appliqué en dernier. [Plus bas](#ce-que-vous-ne-pouvez-pas-surcharger-et-pourquoi).

## 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](design) — 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`.

```yaml
design:
  components:
    table:
      parts:
        root: border-2
        header: uppercase tracking-wide
        cell: font-mono
```

Les 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](/fr/docs/design-system-console) vous dira, partie par partie, de quelle couche vient chaque classe rendue.

### `variants` — des classes pour une variante déclarée

```yaml
design:
  components:
    button:
      variants:
        destructive:
          root: border-2
        ghost:
          root: underline-offset-4
```

Le 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.

```yaml
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é.

```yaml
design:
  components:
    button:
      replace: true
      parts:
        root: px-4 py-2 bg-slate-900 text-white
```

`replace: 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.components` pour 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.className` l'emporte encore aujourd'hui.** Le socle surclasse `design.components`. Un `focus-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](/fr/docs/design-system-console).

## 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.

```yaml
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 :

```yaml
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`](design-type-scale).

## Pages liées

- [Système de design](design) — la clé `design`, et le guidage qui vit sur vos gabarits
- [Échelle typographique](design-type-scale) — l'échelle de titres ordonnée
- [Densité](design-density) — l'échelle de ligne, contrôle, gouttière et petit texte
- [Thème et couleurs](theme) — la palette d'auteur que porte `design.colors`
- [Console du système de design](design-system-console) — la galerie de composants, et les pastilles de provenance par partie
