Un socle, six voix
« Moderne » est le thème de base. Cinq thèmes prêts à l'emploi — Solaire, Luxe,
Créatif, Puissant, Forêt — l'écrasent proprement via data-theme-style,
en clair comme en sombre, sans jamais toucher aux composants. Trois garde-fous
vérifient que ça reste vrai : la charte des tokens, la sémantique HTML et le
contraste WCAG des 348 paires de couleurs que produisent les thèmes.
🏗️ Architecture en 2 couches
Quatre feuilles pour le socle, puis les thèmes voulus — dans cet ordre précis. Chaque fichier a une seule nature, donc une seule raison de changer (voir ARCHITECTURE-CSS.md) :
<link rel="stylesheet" href="styles/primitives.css"> <!-- valeurs brutes, jamais themées -->
<link rel="stylesheet" href="styles/defaults.css"> <!-- les 6 familles par défaut, ce qu'un thème surcharge -->
<link rel="stylesheet" href="styles/components.css"> <!-- éléments HTML, ne lit QUE des tokens sémantiques -->
<link rel="stylesheet" href="styles/layout.css"> <!-- classes de disposition mécaniques, non themées -->
<link rel="stylesheet" href="styles/themes/theme-solaire.css"> <!-- thèmes prêts à l'emploi, chacun -->
<link rel="stylesheet" href="styles/themes/theme-luxe.css"> <!-- optionnel : ne chargez que ceux -->
<link rel="stylesheet" href="styles/themes/theme-creatif.css"> <!-- dont vous avez réellement besoin -->
<link rel="stylesheet" href="styles/themes/theme-puissant.css"> <!-- en production -->
<link rel="stylesheet" href="styles/themes/theme-foret.css"> <!-- (créé avec le créateur de thème) -->
Le thème actif s'active avec deux attributs indépendants sur <html>.
Sans data-theme, le mode suit la préférence système —
pour les six thèmes, sans JavaScript : chaque token qui dépend du mode est écrit
une seule fois en light-dark(clair, sombre), et data-theme
ne fait que figer le color-scheme.
<html> <!-- Moderne · auto : suit la préférence système -->
<html data-theme="light"> <!-- Moderne · clair -->
<html data-theme="dark"> <!-- Moderne · sombre -->
<html data-theme-style="solaire"> <!-- Solaire · auto -->
<html data-theme="light" data-theme-style="solaire"> <!-- Solaire · clair -->
<html data-theme="dark" data-theme-style="luxe"> <!-- Luxe · sombre -->
<html data-theme="dark" data-theme-style="creatif"> <!-- Créatif · sombre -->
<html data-theme="light" data-theme-style="puissant"> <!-- Puissant · clair -->
components.css ne contient aucune couleur, aucun espacement, aucune ombre,
aucune police en dur — seulement des var(--token-sémantique). Un plugin
stylelint vérifie cette règle
automatiquement (voir npm run lint:css), pas seulement dans la documentation.
Les 6 familles, séparées et surchargeables indépendamment
Chaque famille peut être surchargée seule — un thème n'est pas obligé de toutes les redéfinir.
| Famille | Rôle | Exemples de tokens |
|---|---|---|
| Couleurs | Surfaces, texte, bordures, actions, statuts | --surface-base --action-primary --brand-gradient |
| Espaces | Rythme, paddings de composants | --space-card-padding --space-section-gap |
| Layout principal | Largeurs de conteneur, silhouette (rayons) | --layout-content-width --layout-radius-card |
| Ombres | Élévation, halos lumineux | --shadow-elev-mid --shadow-glow-accent |
| Animations | Vitesse, ressort, amplitude | --motion-normal --motion-float-distance |
| Typographie | Police, graisse, interlignage, interlettrage | --font-heading --weight-display --leading-body |
Les 6 thèmes
| Thème | Couleurs | Formes | Ombres | Vitesse | Titres |
|---|---|---|---|---|---|
| Moderne | Indigo / Slate | Coins modérés | Subtiles | Rapide | Neo-Grotesque |
| Solaire | Corail / Argile | Très arrondi | Chaudes, marquées | Lent, rebond | Geometric Humanist |
| Luxe | Or / Encre | Coins nets | Discrètes | Très lent | Didone |
| Créatif | Fuchsia / Raisin | Pilule partout | Colorées, vives | Rapide, rebond | Rounded Sans |
| Puissant | Noir / Cramoisi | Angles droits | Dures, décalées | Immédiate | Industrial |
| Forêt | Vert profond / Sarcelle | Coins modérés | Teintées vert | Posée | Neo-Grotesque |
🎨 Tokens actifs
Ces échantillons lisent les tokens sémantiques — ils changent avec le sélecteur de thème en haut de page.
Couleurs de marque (thémées)
Statuts — universels, ne changent jamais de thème
Typographie — voix du thème actif
--font-headingTitre du thème
--font-bodyCorps de texte du thème actif, pour juger sa lisibilité.
--font-uiLibellé d'interface
--font-codeconst voix = "thème";
Espaces — échelle sémantique
inline-gap
stack-gap
group-gap
card-padding
section-gap
Layout principal — rayons
Ombres — élévation sémantique
🧱 Composants
Le même HTML, sans aucune modification, sous les six thèmes.
Boutons — <button>
Badges — .badge
Formulaires
Checkbox, Radio & Switch
Range & Color
Progress & Meter
Alertes — .alert
Cartes — .card
Carte Interactive
Survolez-moi !
Backdrop-filter blur.
Contour animé au survol.
Avatars
Groupe : AB CD EF GH
Skeleton — aria-busy="true"
Pagination
La page courante porte aria-current="page" : l'information est dans le HTML, le CSS ne fait que la montrer.
Infobulle & activité
L'infobulle est un complément : une information indispensable doit vivre dans le flux, pas dans un pseudo-élément.
Notifications — .toast
En place réelle, dans un .toast-region avec role="status" : l'ajout est annoncé sans voler le focus.
État vide
Panneau latéral — <dialog data-variant="drawer">
Un <dialog> ouvert par showModal() : le navigateur fournit déjà le piège de focus, la fermeture par Échap et le voile. Seule la géométrie change.
Accordéon — <details> / <summary>
Comment fonctionne la surcharge de thème ?
Chaque thème ajoute data-theme-style="nom" sur <html>. Ses sélecteurs ciblent cet attribut avec une spécificité égale ou supérieure à ceux de tokens.css et sont chargés après : ils gagnent la cascade sans !important.
Puis-je ne surcharger que les couleurs ?
Oui. Chacune des 6 familles est indépendante : gardez uniquement la section « Couleurs » d'un fichier de thème et seules les couleurs changeront, le reste (espaces, layout, ombres, animations, typographie) retombera sur « Moderne ».
Comment le garde-fou stylelint m'empêche-t-il de casser ça ?
Le plugin tooling/stylelint-plugin-design-tokens.js interdit d'écrire var(--base-primary-500) ou var(--base-font-didone) (une primitive) directement dans components.css : il faut passer par un token sémantique comme --action-primary ou --font-heading. Il interdit aussi toute couleur littérale (#hex, rgb()…) hors des primitives, et tout sélecteur [data-theme] dans components.css. Lancez npm run lint:css pour le vérifier.
Et si mon thème a besoin de nouvelles couleurs de marque ?
Ajoutez-les en section 0 de votre fichier de thème (ex. --base-solaire-coral-500), comme le fait « Solaire ». Les composants ne les verront jamais directement : ils ne lisent que --action-primary, --brand-gradient, etc.
Citations — <blockquote>
Un design system n'est pas une palette figée, c'est une indirection bien placée.
Architecture des tokens
Cinq thèmes actifs, zéro ligne de components.css modifiée.
Séparateurs — <hr>
Tables — <table>
| Attribut | Rôle |
|---|---|
data-theme | Mode clair (light) ou sombre (dark) |
data-theme-style | Absent = « Moderne » · solaire/luxe/creatif/puissant = thème actif |
Code
Inline : --action-primary change avec la marque active. Raccourcis : ⌘ + K
Modale — <dialog>
Navigation — <nav>
✨ Animations — data-animate
Les mêmes attributs, mais chaque thème a sa propre vitesse et son propre ressort — de l'immédiat (Puissant) au très lent (Luxe).
📋 Créer son propre thème
<!-- 1. Copiez un thème proche de ce que vous voulez -->
<!-- 2. Remplacez data-theme-style="solaire" par votre nom -->
[data-theme-style="mon-theme"] {
/* Un seul bloc : light-dark() porte les deux modes. */
--action-primary: light-dark(var(--ma-marque-600), var(--ma-marque-400));
/* … uniquement les familles que vous voulez changer … */
}
<!-- 3. Chargez-le après components.css -->
<link rel="stylesheet" href="styles/themes/theme-mon-theme.css">
<!-- 4. Activez-le (sans data-theme : suit la préférence système) -->
<html data-theme-style="mon-theme">
# 5. Vérifiez la charte, les contrastes et le reste
npm test