Personnaliser l'apparence

Changer l'accent, le bouton principal, les gris, les arrondis, la barre et la coquille de l'admin depuis un seul fichier, theme.js.

Un projet Fraza part en noir, blanc et gris, avec un seul accent, le bleu fleuve. Ce guide montre comment lui donner sa propre apparence, de la couleur d'un lien jusqu'à la disposition de l'admin, en modifiant un seul fichier. Tout suit en même temps : l'admin, les pages publiques, les modules, les courriels et les PDF.

Le fichier theme.js

npx @fraza/start génère theme.js à la racine du projet, avec les valeurs par défaut. instrumentation.js le passe à initializeFraza, et <ThemeStyle /> l'applique dans le <head> du layout racine.

// theme.js
import { defineTheme } from '@fraza/core/themes/define';

export default defineTheme({
  // Bouton principal : 'neutral' (encre noire, blanche en sombre) ou une couleur hex.
  primary: 'neutral',
  // Accent en hex : liens, focus, cases cochées, élément actif.
  accent: '#2457c5',
  // Teinte des gris : 'neutral', 'stone', 'slate', 'zinc' ou 'gray'.
  neutral: 'neutral',
  // Arrondis : 'none', 'sm', 'md' ou 'lg'.
  radius: 'md',
  // Barre supérieure et sidebar : 'light' (suit le thème) ou 'dark' (toujours foncée).
  chrome: 'light',
  // Coquille de l'administration : 'sidebar' ou 'topbar'.
  shell: 'sidebar',
  // Modes offerts. ['light'] ou ['dark'] impose un mode et masque la bascule.
  modes: ['light', 'dark'],
});

Les valeurs du fichier généré sont les défauts : supprimer une ligne revient au même.

Redémarrer le serveur de développement après une modification : le thème est lu au boot, comme les modules.

Chaque option est un choix borné. Une valeur hors liste fait échouer le démarrage avec un message qui nomme les valeurs admises. C'est voulu : un thème libre permettrait de tout faire, y compris un admin illisible. Le détail des options est dans Thèmes.

Choisir l'accent

accent prend une seule couleur hex. C'est la couleur qui guide l'œil : liens, anneau de focus, bordure du champ actif, cases cochées, interrupteurs activés, élément actif du menu, statut « info ». Le core en déduit le reste :

  • le survol des liens et des éléments accentués ;
  • le fond pâle de l'élément actif du menu et des pastilles d'information ;
  • une version plus claire pour le mode sombre, où une couleur foncée se perdrait sur le noir ;
  • le texte posé sur l'accent plein, blanc ou noir, selon ce qui se lit le mieux.
export default defineTheme({
  accent: '#0f766e',
});

L'accent doit garder un contraste d'au moins 4,5:1 sur blanc. C'est le seuil qui rend un lien lisible en petit texte. Une couleur trop pâle, un jaune ou un vert clair, est assombrie au démarrage jusqu'à l'atteindre, et fraza affiche un avertissement qui nomme la valeur donnée, son contraste et la valeur retenue :

⚠ defineTheme: `accent` #ffcc00 est trop pâle sur blanc (1.51:1, minimum 4.5:1). #907200 est utilisé en clair.

Le démarrage continue. Pour faire taire l'avertissement, choisir une couleur assez foncée, ou écrire directement la valeur proposée. La version sombre part de la couleur d'origine, éclaircie s'il le faut pour se lire sur le noir.

L'accent reste rare. Il ne va pas sur les boutons principaux, les titres ou les grandes surfaces : une interface où tout prend l'accent ne montre plus où cliquer.

Changer le bouton principal

Par défaut, le bouton principal est à l'encre : noir en clair, blanc en sombre. primary accepte une couleur hex pour lui donner la couleur de la marque :

export default defineTheme({
  primary: '#1d6fd8',
});

Le core en déduit le survol, une version plus claire pour le sombre et le texte posé dessus. primary ne touche qu'au bouton principal. Seule exception : sans option accent, l'accent est tiré de primary, assombri s'il le faut pour rester lisible sur blanc. Pour un bouton coloré et un accent bleu fleuve, écrire les deux options.

Changer la disposition de l'admin

Deux options touchent à la structure, pas seulement aux couleurs.

chrome: 'dark' garde la barre du haut et la sidebar foncées, quel que soit le mode. Le contenu garde ses surfaces claires. Prévoir FRAZA_LOGO_URL_DARK si le logo est foncé : c'est lui qui s'affiche sur une barre foncée.

shell: 'topbar' retire la sidebar et met la navigation dans la barre du haut. Elle convient à une application aux sections peu nombreuses, un portail client par exemple. Sur mobile, le bouton de menu ouvre le même tiroir que la coquille sidebar.

Un module peut poser un élément dans cette barre, une cloche de notifications par exemple, avec registerTopbarItem.

Exemple : un portail client

Un portail pour les clients d'une firme d'ingénierie : barre foncée, navigation en haut, accent bleu de la firme, gris chauds, fond de page légèrement crème, uniquement en clair.

// theme.js
import { defineTheme } from '@fraza/core/themes/define';

export default defineTheme({
  accent: '#1d6fd8',
  neutral: 'stone',
  chrome: 'dark',
  shell: 'topbar',
  modes: ['light'],
  overrides: {
    light: { canvas: '#f3f2ef' },
  },
});

modes: ['light'] impose le clair et masque toutes les bascules, dans l'admin comme sur les pages publiques. overrides fixe la valeur exacte d'un jeton quand le calcul par défaut ne convient pas. Les clés sont limitées aux jetons connus, listés dans Thèmes. Changer la couleur d'un statut, danger par exemple, suffit : son fond pâle, sa bordure, son encre et son survol sont recalculés à partir d'elle, sauf ceux que overrides donne aussi.

Les cartes de projet, les décisions en attente et le fil d'activité d'un tel portail ne relèvent pas du thème. Ce sont des widgets de tableau de bord, fournis par un module du projet. Voir Étendre l'admin.

Écrire un composant qui suit le thème

Un composant du projet, un bloc ou un gabarit public, écrit des jetons, jamais une nuance de palette :

// ✓ suit l'accent, le clair et le sombre
<div className="rounded-card border border-line bg-surface p-5">
  <h2 className="text-fg font-semibold">Projets</h2>
  <p className="text-fg-muted text-sm">3 en cours</p>
  <a className="text-accent" href="/projets">Voir tout</a>
</div>

// ✗ reste noir et blanc, et oublie le sombre dès qu'une variante manque
<div className="rounded-xl border border-neutral-200 bg-white dark:bg-neutral-900">

Pour une valeur qui n'est pas une classe, un style inline ou un SVG, lire la variable CSS du jeton : var(--fraza-fg-subtle). La liste des jetons et de leur rôle est dans le design system.

Courriels et PDF

Rien à faire côté projet. Les courriels écrivent les mêmes jetons, que BaseLayout résout en hex dans les valeurs du mode clair. Un PDF lit getThemeValues('light') au rendu. Voir Courriel.

Dernier recours : le CSS

Le thème couvre l'accent, le bouton principal, les gris, les arrondis, les polices et la disposition. Pour un besoin qu'il ne couvre pas, le globals.css du projet reste disponible, après l'import de @fraza/core/styles/core.css.

Pour changer la valeur d'un jeton, passer par overrides plutôt que par le CSS. Les valeurs du thème sont injectées dans le <head>, et une redéfinition de --fraza-* dans une feuille du projet n'est pas assurée de passer devant. Surtout, overrides est validé au démarrage, ce qu'aucune règle CSS ne sera.

Notes

Un projet créé avant le thème injecte encore THEME_INIT_SCRIPT et n'a pas de theme.js. fraza-doctor le signale, et npx fraza-doctor --fix fait la migration : il crée theme.js avec les valeurs par défaut, et corrige sur place instrumentation.js et app/layout.js sans toucher au reste de ces fichiers. Quand un de ces fichiers a été trop remanié pour être reconnu, le doctor n'y touche pas et liste les lignes à ajouter.

Les blocs, gabarits et pages publiques du projet gardent leurs anciennes classes. Ils s'affichent comme avant, mais ne suivent le thème qu'une fois réécrits avec les jetons. La déclaration @custom-variant dark et les variables --fraza-bg / --fraza-text d'un ancien globals.css peuvent rester : le core déclare désormais le variant lui même, et les jetons ne dépendent pas de ces variables.

Sur cette page