Aller au contenu

Design system : tokens, thèmes et accent

Référence du design system (DS) de Kirexo : où vivent les fichiers, comment le thème clair/sombre et l'accent d'instance sont pilotés, quelles variables CSS sont disponibles, et quelles contraintes d'accessibilité sont garanties. Pour le raisonnement derrière l'accent rendu côté serveur, voir l'explication Pourquoi un accent d'instance piloté serveur.

Fichiers du DS et emplacements

Le DS a une source de vérité versionnée dans design-system/, et des copies de travail effectivement servies par l'application. La source de vérité ne doit pas être réécrite : les copies en sont issues.

Rôle Source de vérité (versionnée) Copie de travail (servie)
Feuille de tokens et composants design-system/kirexo-ds.css assets/styles/kirexo-ds.css
Feuille des écrans design-system/screens.css assets/styles/screens.css
Script d'accent + logomark design-system/accent.js assets/js/accent.js (copie adaptée, voir plus bas)
Script de thème clair/sombre assets/js/theme.js
Favicon et manifest design-system/favicon/ public/favicon/

Le dossier public/favicon/ contient : favicon.ico, favicon-16.png, favicon-32.png, favicon-48.png, favicon-180.png, favicon-192.png, favicon-512.png, favicon-mono-512.png, safari-pinned-tab.svg, site.webmanifest.

accent.js est une copie adaptée

Contrairement aux CSS et au favicon copiés tels quels, assets/js/accent.js est dégraissé de la persistance et de la ré-application de l'accent depuis localStorage présentes dans la source. La raison est expliquée dans Pourquoi un accent d'instance piloté serveur.

Thème clair/sombre — data-theme

Le thème est porté par l'attribut data-theme sur <html>, avec deux valeurs admises :

Valeur Effet
light Palette claire (valeur par défaut, équivalente à :root dans assets/styles/kirexo-ds.css)
dark Palette sombre

Le rendu serveur pose data-theme="light" par défaut. Le script assets/js/theme.js ajuste ensuite la valeur côté client :

  • il lit la clé localStorage kirexo-theme (préférence locale du visiteur) ;
  • en l'absence de préférence enregistrée, il suit prefers-color-scheme ;
  • il bascule l'attribut data-theme sur <html> en conséquence.

Le thème est donc une préférence par visiteur, persistée dans le navigateur.

Accent d'instance — data-accent

L'accent est porté par l'attribut data-accent sur <html> et rendu côté serveur. Les six valeurs admises correspondent aux cases de l'enum App\Entity\Enum\AccentColor (src/Entity/Enum/AccentColor.php) :

Valeur (data-accent) Libellé FR (AccentColor::label())
encre Encre (valeur par défaut)
bordeaux Bordeaux
foret Forêt
prune Prune
rouille Rouille
graphite Graphite

Contrairement au thème, l'accent n'est pas une préférence par visiteur : c'est un réglage d'instance, partagé par tous. Voir Pourquoi un accent d'instance piloté serveur.

Fonction Twig platform_accent()

La valeur de data-accent est injectée par la fonction Twig platform_accent(), utilisée dans templates/base.html.twig :

<html lang="fr" data-theme="light" data-accent="{{ platform_accent() }}">

La fonction est exposée par src/Twig/PlatformAccentExtension.php et déléguée à src/Twig/Runtime/PlatformAccentRuntime.php. Le runtime lit le réglage via SettingRepositoryInterface (src/Repository/SettingRepositoryInterface.php) : il retourne la valeur de l'accent du singleton Setting, ou la valeur par défaut (encre) si le singleton n'a pas encore été initialisé.

Entité Setting et enum AccentColor

L'accent est persisté par l'entité singleton App\Entity\Setting (src/Entity/Setting.php) — une seule ligne en base, dans la table setting. Elle porte une propriété accent typée par l'enum AccentColor, stockée via le mapping Doctrine enumType.

L'enum App\Entity\Enum\AccentColor (src/Entity/Enum/AccentColor.php) est un backed enum string exposant deux méthodes :

Méthode Rôle
label(): string Libellé FR affichable (Encre, Bordeaux, Forêt, Prune, Rouille, Graphite)
default(): self (statique) Valeur par défaut centralisée — AccentColor::Encre

L'accès au singleton se fait par SettingRepositoryInterface :

  • getSingleton(): ?Setting — lecture (peut être null avant initialisation) ; utilisé par le runtime Twig ;
  • getOrCreate(): Setting — lecture-ou-création, jamais null ; réservé aux mutations (page Réglages à venir) ;
  • save(Setting $setting, bool $flush = true): void — persistance.

Variables CSS principales

Les couleurs et la typographie passent exclusivement par des variables CSS définies dans assets/styles/kirexo-ds.css. Aucune valeur hexadécimale en dur dans un template ou une CSS applicative.

Variable Rôle
var(--ink) Couleur de texte principale
var(--ink-2), var(--ink-3) Texte secondaire et tertiaire
var(--surface) Surface principale
var(--surface-2), var(--surface-3) Surfaces secondaires
var(--paper) Fond de page
var(--line), var(--line-2) Bordures et séparateurs
var(--accent) Couleur d'accent (dérivée de data-accent)
var(--accent-hover) Accent au survol
var(--accent-ink) Texte sur fond d'accent
var(--accent-soft), var(--accent-line) Accent atténué et bordure d'accent
var(--font-serif) Typo des corps d'article (Newsreader)
var(--font-sans) Typo de l'interface (Public Sans)

Chaque variable est redéfinie pour [data-theme="dark"] : changer data-theme suffit à basculer toute la palette.

Classes d'écran — Édition d'article

L'écran d'édition d'article (espace utilisateur) habille son markup via des classes définies dans la feuille des écrans design-system/screens.css (source) et sa copie servie assets/styles/screens.css. Toutes s'appuient exclusivement sur les variables CSS du DS — aucun hex en dur, donc thème clair/sombre et les six accents sont gérés automatiquement par les tokens.

Classe Rôle
.article-edit Wrapper de la page de l'éditeur (colonne centrée, espacements verticaux).
.article-editor Hôte de l'éditeur Milkdown : corps d'article rendu en var(--font-serif), sur var(--surface) bordé et ombré.
.article-toolbar Barre de formatage au-dessus de l'éditeur. Les boutons sont ciblés par le sélecteur descendant .article-toolbar button (cibles ≥ 44px, anneau :focus-visible préservé) ; l'état actif est porté par .article-toolbar button[aria-pressed="true"] (fond var(--accent), texte var(--accent-ink)).
.article-status Zone d'état de l'enregistrement automatique (autosave), annoncée en aria-live. Hauteur minimale fixe pour éviter le décalage de mise en page.
.article-actions Conteneur des formulaires du cycle de vie de l'article (publier, republier, corbeille…).
.article-unpublish Formulaire de dépublication inline. Le couple label/champ URL est habillé en .field / .input pour la parité visuelle avec le form theme du DS (le champ est du HTML écrit à la main, les classes y sont posées explicitement).

Le hook JS .js-article-editor (attaché via data-controller) n'est pas une cible de style : aucune règle CSS ne lui est attachée.

Classes d'écran — Back-office (réglages, articles, analyse IA)

Les écrans d'administration (réglages, liste et détail d'articles, panneau d'analyse IA) sont habillés par des classes préfixées bo- définies dans la feuille des écrans design-system/screens.css (source) et sa copie servie assets/styles/screens.css. Comme partout dans le DS, elles s'appuient exclusivement sur les variables CSS : thème clair/sombre et les six accents sont gérés automatiquement par les tokens.

Familles de classes

Famille Écran Rôle
.bo-settings__api, .bo-field Réglages (templates/admin/settings.html.twig) Section « Clé API Claude » sous l'accent (.bo-settings__api) et wrapper label/champ d'un formulaire (.bo-field). L'<input> lui-même est habillé en .input par le form theme du DS, pas par .bo-field.
.bo-articles-index* Liste d'articles (templates/admin/article/index.html.twig) En-tête (__head), sous-titre (__lead) et état vide (__empty) de la liste. La table réutilise .bo-table / .bo-articles.
.bo-article-show* Détail d'article (templates/admin/article/show.html.twig) Conteneur de lecture et ses parties : en-tête (__head), méta auteur/date (__meta), corps publié en var(--font-serif) (__body), section d'analyse (__analysis), note clé API absente (__hint).
.bo-analysis* Panneau analyse IA (templates/admin/article/_analysis.html.twig) Form déclencheur (__form), rapport en carte (__report), avertissement non juridique (__disclaimer), résumé (__summary), chips de thèmes (__themes), grille de conformité (__grid), horodatage (__date).

Badges de risque .bo-risk--*

Le panneau d'analyse IA affiche un niveau de risque sous forme de badge. Le modificateur reprend la valeur de l'enum App\Entity\Enum\RiskLevel (src/Entity/Enum/RiskLevel.php) — d'où l'underscore de a_verifier. Chaque niveau mappe vers un couple de tokens sémantiques :

Classe Niveau (RiskLevel) background color
.bo-risk — (badge de base)
.bo-risk--aucun None (aucun) var(--surface-2) var(--ink-2)
.bo-risk--faible Low (faible) var(--ok-soft) var(--ok)
.bo-risk--a_verifier ToVerify (a_verifier) var(--warn-soft) var(--warn)
.bo-risk--eleve High (eleve) var(--danger-soft) var(--danger)

aucun est volontairement neutre (gris --ink-2, « rien à signaler ») et non vert : le vert --ok est réservé à faible (risque présent mais mineur).

Préfixe de classes admin : bo-

Les classes propres à l'espace d'administration (back-office) utilisent le préfixe bo- (ex. bo-space__main), jamais ad- — un préfixe ad- est fréquemment bloqué par les bloqueurs de publicité. L'espace utilisateur utilise des classes sans préfixe bo-.

Contraintes RGAA

Le DS garantit plusieurs règles d'accessibilité, à ne jamais contourner :

  • Anneau de focus — l'anneau :focus-visible n'est jamais retiré. La règle vit dans assets/styles/kirexo-ds.css.
  • Lien d'évitement — un skip-link .skip-link (« Aller au contenu ») est le premier enfant du <body> dans templates/base.html.twig et cible #main. Le <main> des layouts utilisateur et admin porte donc id="main".
  • Cibles tactiles ≥ 44px — les composants interactifs du DS (.btn, .input…) garantissent une hauteur minimale de 44px.

Chargement des assets

Les CSS et les scripts du DS sont importés depuis assets/app.js :

import './styles/kirexo-ds.css';
import './styles/screens.css';
import './js/accent.js';
import './js/theme.js';

app.js est le seul entrypoint injecté dans templates/base.html.twig via {{ importmap('app') }}. Cet appel injecte automatiquement les <link> des CSS importés — il ne faut donc pas lier kirexo-ds.css une seconde fois via asset() (double chargement). Les deux modules accent.js et theme.js sont déclarés dans importmap.php comme modules importables (sans entrypoint), pour être résolus et versionnés par AssetMapper, et sont chargés sur toute page héritant du layout via les import d'app.js.

Voir aussi