Chapitre 8.1 — Comment fonctionne un thème Moodle
Partie 8 : Les thèmes — Chapitre 1/5 Public : dev front (React/Next.js, SCSS/Tailwind). On pose les fondations avant de construire
theme_maboite(ch. 8.2). Version de référence : Moodle 5.2 (thème Boost basé sur Bootstrap 5.3, sans jQuery ; scssphp côté serveur).
⏱️ TL;DR
- Un thème est un plugin (
theme_*) qui contrôle 100 % du CSS, du HTML (via templates) et du JS de l’interface, sans jamais toucher au core.- Boost est le thème de référence de Moodle 5.2 (Classic existe encore). On crée quasi toujours un thème enfant de Boost (héritage de
parents) plutôt que de partir de zéro.- Le
config.phpdu thème déclare :name,parents(héritage),sheets,scss(callbacks de compilation),layouts,rendererfactory,requiredblocks… C’est la carte d’identité du thème.- Le pipeline SCSS a trois étages : pre-SCSS (variables injectées avant) → preset (le gros du SCSS, dont Bootstrap) → extra-SCSS (surcharges après). On personnalise surtout en redéfinissant des variables Bootstrap dans le pre-SCSS.
- Le SCSS est compilé côté serveur par scssphp et mis en cache. En dev,
themedesignermodedésactive ce cache (au prix de la lenteur) pour itérer.- Sélection du thème possible à plusieurs niveaux : site, catégorie, cours, cohorte, utilisateur, URL (device themes = vestige).
- Un thème hérite de son parent : tout ce qu’il ne surcharge pas vient de Boost (templates, layouts, renderers, SCSS). C’est ce qui rend un thème enfant léger et maintenable.
🎯 Objectifs
- Comprendre qu’un thème est un plugin qui surcharge le rendu du core, sans le modifier.
- Lire un
config.phpde thème ligne par ligne. - Maîtriser le pipeline SCSS en trois étages et la surcharge des variables Bootstrap.
- Savoir comment le SCSS est compilé et mis en cache, et comment itérer en dev.
- Connaître les niveaux de sélection d’un thème.
1. Ce qu’un thème peut faire (et la vraie question)
La question que se pose tout dev front arrivant sur Moodle : « suis-je libre de faire l’UI que je veux ? ». Réponse courte, développée au chapitre 8.4 : oui, radicalement — un thème peut atteindre 100 % du HTML (via les templates Mustache, ch. 7.2), 100 % du CSS (via SCSS + variables Bootstrap) et 100 % du JS (modules AMD), sans jamais modifier le code du core (interdit, ch. 6.1).
Mais cette liberté a une structure (héritage, pipeline SCSS, surcharges par convention) et un coût (maintenir des surcharges à travers les upgrades). Ce chapitre installe la structure ; le coût est le sujet du 8.4.
💡 Pour un dev React : un thème Moodle combine trois choses que vous connaissez : un design system (SCSS + variables Bootstrap ≈ vos design tokens Tailwind), des composants (templates Mustache ≈ vos composants de présentation) et des layouts (≈ les layouts Next.js). La différence fondamentale : vous ne possédez pas l’arbre de rendu. Vous surchargez celui du core, fichier par fichier, par convention de nommage — comme du component shadowing (ch. 7.2, §5).
2. Boost, Classic, et l’héritage de thèmes
Moodle 5.2 livre deux thèmes :
- Boost — le thème par défaut, moderne, basé sur Bootstrap 5.3, avec l’index de cours, les drawers, l’interface réactive. C’est le parent naturel de tout nouveau thème.
- Classic — un thème plus « traditionnel » (colonnes de blocs classiques), maintenu mais moins mis en avant.
Le mécanisme central est l’héritage : un thème déclare des parents, et hérite de tout ce qu’il ne redéfinit pas. Un thème enfant de Boost récupère automatiquement ses templates, ses layouts, ses renderers, son SCSS (dont Bootstrap), son JS. Vous ne surchargez que le delta.
⚠️ Piège : ne créez jamais un thème « from scratch » sans parent (sauf besoin extrême, ch. 8.4). Sans hériter de Boost, vous devez fournir vous-même tous les layouts, tous les templates, tout le SCSS — des dizaines de milliers de lignes, à re-maintenir à chaque version de Moodle. Un thème enfant de Boost fait souvent moins de 20 fichiers.
3. Anatomie d’un thème : l’arborescence
public/theme/maboite/
├── version.php ← identité (component = theme_maboite)
├── config.php ← LA carte d'identité du thème (le fichier clé)
├── lib.php ← callbacks (SCSS, etc.)
├── settings.php ← réglages admin du thème
├── scss/
│ ├── preset/
│ │ └── default.scss ← preset (le gros SCSS, souvent basé sur Boost)
│ ├── pre.scss ← variables injectées AVANT le preset
│ └── post.scss ← SCSS injecté APRÈS le preset
├── templates/ ← surcharges de templates Mustache (ch. 8.3)
├── layout/ ← layouts PHP (ch. 8.3)
├── classes/output/ ← renderers surchargés (ch. 8.3)
├── pix/ ← images du thème (logo, favicon…)
├── lang/en/theme_maboite.php
└── style/ ← CSS statique éventuel (rare, SCSS préféré)Le fichier central est config.php : tout part de lui.
4. config.php décortiqué
<?php
// public/theme/maboite/config.php
defined('MOODLE_INTERNAL') || die();
// Nom interne du thème (doit matcher le dossier).
$THEME->name = 'maboite';
// HÉRITAGE : on hérite de Boost. Tout ce qu'on ne surcharge pas vient de lui.
$THEME->parents = ['boost'];
// Feuilles de style CSS statiques (rare ; on préfère le SCSS). Vide ici.
$THEME->sheets = [];
// Le thème utilise le SCSS de Boost comme base, transformé par nos callbacks (lib.php).
$THEME->scss = function($theme) {
return theme_maboite_get_main_scss_content($theme);
};
// Callbacks pre/post SCSS (les trois étages du pipeline, §5).
$THEME->prescsscallback = 'theme_maboite_get_pre_scss';
$THEME->extrascsscallback = 'theme_maboite_get_extra_scss';
// Layouts : on réutilise ceux de Boost (hérités). On pourrait en surcharger.
$THEME->layouts = [];
// Blocs requis / régions par défaut : hérités de Boost.
$THEME->requiredblocks = '';
// Fabrique de renderers : permet de surcharger des renderers PHP (ch. 8.3).
$THEME->rendererfactory = 'theme_overridden_renderer_factory';
// Le thème est-il utilisable ? (contrôles de compatibilité)
$THEME->usefallback = true;
// Icône system : FontAwesome 6 (hérité de Boost).
$THEME->iconsystem = \core\output\icon_system::FONTAWESOME;
// Prise en charge du sélecteur de preset dans les réglages, dark mode, etc.
$THEME->haseditswitch = true;Les champs à retenir :
| Champ | Rôle |
|---|---|
name | identité, = dossier |
parents | héritage (Boost) — le champ le plus important |
scss | le SCSS principal du thème (souvent : « prends Boost + mon preset ») |
prescsscallback | injecte des variables SCSS avant le preset (surcharge Bootstrap) |
extrascsscallback | injecte du SCSS après le preset (surcharges finales) |
layouts | mappage type-de-page → fichier de layout (hérité si vide) |
rendererfactory | active la surcharge des renderers PHP |
iconsystem | FontAwesome 6 |
💡 Pour un dev React :
config.phpest letheme.configde votre design system + le manifeste du thème.parents= « j’étends ce thème de base » (héritage de configuration),scss/prescss/extrascss= les hooks d’injection de tokens et de styles,rendererfactory= l’autorisation de « shadow » des composants PHP. Tout le reste (templates, layouts) est hérité par défaut et surchargé à la demande.
5. Le pipeline SCSS : trois étages
C’est le cœur technique de la personnalisation visuelle. Le SCSS final compilé est l’assemblage de trois morceaux, dans cet ordre :
5.1 Étage 1 — pre-SCSS : redéfinir les variables Bootstrap
Injecté avant tout le reste. C’est là qu’on surcharge les variables Bootstrap ($primary, $font-family-base, $border-radius…) : comme Bootstrap est importé après, il utilise vos valeurs. C’est la technique de personnalisation la plus puissante et la moins coûteuse.
// contenu renvoyé par theme_maboite_get_pre_scss (ex.)
$primary: #5940c1; // couleur de marque MaBoîte
$font-family-base: 'Inter', sans-serif;
$border-radius: 0.5rem;Changer $primary avant l’import de Bootstrap re-teinte automatiquement boutons, liens, badges, focus… partout dans Moodle, car tous ces composants dérivent leur couleur de $primary. Un seul token, tout le site suit.
5.2 Étage 2 — preset : le gros du SCSS
Le preset est le SCSS principal : il importe Bootstrap et le SCSS de Boost. Un thème enfant réutilise en général le preset de Boost (default.scss) via un callback qui charge le contenu de Boost + le sien. On peut fournir plusieurs presets sélectionnables dans les réglages (un « preset picker », ch. 8.2).
5.3 Étage 3 — extra-SCSS : les surcharges finales
Injecté après Bootstrap. Pour les règles CSS qui ne passent pas par une variable (styliser un composant précis, ajouter des classes). Comme il vient en dernier, il gagne (spécificité/ordre).
// contenu renvoyé par theme_maboite_get_extra_scss (ex.)
.navbar-brand img { max-height: 40px; }
#page-footer { background: darken($primary, 10%); color: #fff; }⚠️ Piège de l’ordre : redéfinir
$primarydans l’extra-SCSS (étage 3) ne fonctionne pas pour re-teinter Bootstrap : à ce stade, Bootstrap a déjà été compilé avec l’ancienne valeur. Les variables vont dans le pre-SCSS (étage 1) ; les règles CSS dans l’extra-SCSS (étage 3). Confondre les deux est l’erreur n°1 des débutants du SCSS Moodle.
6. Compilation et cache : scssphp et themedesignermode
Moodle compile le SCSS côté serveur avec scssphp (un compilateur SCSS en PHP — pas besoin de Node pour un thème !). Le CSS résultant est mis en cache : compilé une fois, servi mille fois. C’est essentiel pour la performance (recompiler à chaque requête serait catastrophique).
Conséquence pour le développement : vos modifications SCSS ne s’appliquent pas tant que le cache n’est pas invalidé. Deux solutions :
themedesignermode(Administration → Apparence → Réglages des thèmes, ou$CFG->themedesignermode = true;) : désactive le cache du thème → chaque rechargement recompile le SCSS. Indispensable en dev, mais lent (recompilation à chaque page) → à désactiver en prod.- Purger les caches (
php admin/cli/purge_caches.php) après un changement, si vous n’utilisez pasthemedesignermode.
⚠️ Piège n°1 du dev de thème : « mon changement de couleur ne s’applique pas ». 9 fois sur 10 : le cache du thème. En dev, activez
themedesignermode; sinon purgez les caches. Et n’oubliez pas que le navigateur cache aussi le CSS (rechargement forcé / cache désactivé dans les DevTools).
💡 Pour un dev React : la compilation SCSS serveur + cache remplace votre pipeline
postcss/vite. Pas de build Node pour un thème « SCSS pur » : scssphp fait tout côté PHP.themedesignermodeest votre « mode dev sans cache » (comme désactiver le cache de build), à ne jamais laisser en prod (chaque page recompilerait le CSS).
7. Où le thème s’applique : les niveaux de sélection
Moodle peut appliquer des thèmes différents selon le contexte, par ordre de priorité (du plus spécifique au plus général) :
| Niveau | Exemple d’usage | Activé par |
|---|---|---|
| URL | forcer un thème via ?theme= (dev/test) | $CFG->allowthemechangeonurl |
| Utilisateur | un thème par utilisateur | $CFG->allowuserthemes |
| Cohorte | un thème par groupe d’utilisateurs | réglage cohorte |
| Cours | thème propre à un cours | $CFG->allowcoursethemes |
| Catégorie | thème par catégorie de cours | $CFG->allowcategorythemes |
| Site | le thème par défaut | Apparence → Sélecteur de thèmes |
En pratique, la plupart des sites utilisent un seul thème au niveau site. Les niveaux catégorie/cours/cohorte servent au multi-marque (une plateforme, plusieurs identités visuelles — ch. 8.4). Les device themes (thème mobile web distinct) sont vestigiaux en 5.2 : l’app mobile officielle a son propre rendu (ch. 8.5), et le web est responsive par Bootstrap.
⚠️ Piège : plus vous autorisez de niveaux de sélection (utilisateur, cours, catégorie…), plus vous multipliez les combinaisons de CSS à compiler et mettre en cache et la complexité de support. N’activez que les niveaux réellement nécessaires. Pour un design unique, site suffit.
8. Récapitulatif : le modèle mental du thème
Le thème Moodle repose sur deux idées : hériter de Boost (ne redéfinir que le delta) et injecter/surcharger à des points conventionnels (variables SCSS, templates, renderers, layouts). Tout le pouvoir du chapitre 8.4 (« on est très flexible ») découle de ces deux idées, et tout son coût (maintenir les surcharges) aussi. Le chapitre suivant construit un vrai thème enfant de A à Z ; le 8.3 pousse les surcharges ; le 8.4 fait le bilan flexibilité/coût.
✏️ Exercices
Exercice 1 — Pourquoi un parent ?
Un développeur veut « repartir propre » et crée un thème sans parents. Quelles conséquences concrètes, et que recommandez-vous ?
✅ Solution
Sans parents, le thème n’hérite de rien : il doit fournir tous les layouts, tous les templates Mustache et tout le SCSS (dont Bootstrap et l’interface complète de Moodle) — des dizaines de milliers de lignes, à re-maintenir à chaque version de Moodle (deux fois par an). C’est ingérable. Recommandation : $THEME->parents = ['boost'] et ne surcharger que le delta (couleurs, quelques templates). Un thème enfant fait souvent moins de 20 fichiers et survit bien mieux aux upgrades. (Le cas rare « from scratch » est discuté au 8.4 — et déconseillé.)
Exercice 2 — Où va $primary ?
Vous voulez changer la couleur primaire du site. Dans quel étage du pipeline SCSS mettez-vous $primary: #5940c1; et pourquoi pas ailleurs ?
✅ Solution
Dans le pre-SCSS (étage 1, prescsscallback), avant l’import de Bootstrap. Ainsi Bootstrap se compile avec votre valeur, et tous ses composants (boutons, liens, badges, focus…) en héritent automatiquement. Mettre $primary dans l’extra-SCSS (étage 3) ne marche pas : Bootstrap est déjà compilé avec l’ancienne valeur à ce stade. Règle : variables → pre-SCSS ; règles CSS → extra-SCSS.
Exercice 3 — Le changement ne s’applique pas. Vous modifiez le SCSS, rechargez, rien ne change. Diagnostic et solution dev/prod ?
✅ Solution
Le CSS du thème est mis en cache (compilé par scssphp une fois). En dev : activez themedesignermode pour recompiler à chaque page (lent mais immédiat). En prod (ou sans designer mode) : purgez les caches (php admin/cli/purge_caches.php). Vérifiez aussi le cache navigateur (rechargement forcé). Et n’oubliez pas : themedesignermode doit être désactivé en prod (sinon chaque page recompile le CSS).
Exercice 4 — Un seul token, tout le site.
Expliquez pourquoi changer $primary re-teinte automatiquement des dizaines de composants sans les cibler un par un.
✅ Solution
Parce que Bootstrap (et Boost) dérivent les couleurs de leurs composants à partir de la variable $primary (via theme-color, $link-color, états de focus, etc.). En redéfinissant $primary avant la compilation de Bootstrap, toutes ces dérivations recalculent avec votre valeur. C’est l’intérêt d’un design system à variables : un token central propage sa valeur dans tout le système, sans surcharge CSS manuelle. C’est l’équivalent de vos design tokens Tailwind/CSS variables.
Exercice 5 — Multi-marque. Un client veut une seule plateforme mais deux identités visuelles selon la catégorie de cours (deux écoles). Quel niveau de sélection, et quelle précaution ?
✅ Solution
Activer les thèmes par catégorie ($CFG->allowcategorythemes) et affecter un thème à chaque catégorie d’école. Précaution : chaque thème supplémentaire multiplie le CSS à compiler/mettre en cache et la surface de maintenance ; n’activez que ce niveau (pas utilisateur/cours si inutile), et privilégiez des thèmes enfants du même parent partageant l’essentiel, ne différant que par le pre-SCSS (couleurs/logo). Le vrai multi-tenant est traité au 8.4.
🧠 Quiz de révision
Q1. Qu’est-ce qu’un thème peut modifier, et que ne doit-il jamais toucher ?
Réponse
Q2. Quel est le champ le plus important de config.php et pourquoi ?
Réponse
$THEME->parents (ex. ['boost']) : il définit l’héritage. Le thème récupère automatiquement tout ce qu’il ne surcharge pas (templates, layouts, renderers, SCSS), ce qui rend un thème enfant léger et maintenable.Q3. Décrivez les trois étages du pipeline SCSS et ce qui va dans chacun.
Réponse
$primary, $font-family-base…). 2. preset : le gros SCSS (Bootstrap + Boost). 3. extra-SCSS (après) : les règles CSS finales qui ne passent pas par une variable. Ordre crucial : variables en pre, règles en extra.Q4. Comment le SCSS est-il compilé, et comment itère-t-on en dev ?
Réponse
themedesignermode pour recompiler à chaque page (à désactiver en prod, car lent) ; sinon on purge les caches après chaque modification.Q5. Citez trois niveaux de sélection d’un thème et l’usage typique du multi-niveaux.
Réponse
Chapitre suivant : 02 — Tutoriel : créer theme_maboite, enfant de Boost, de A à Z — chaque fichier complet et copiable : version.php, config.php, lib.php avec les callbacks SCSS, la page de réglages (preset picker, couleur de marque, logo), le SCSS d’exemple (Inter, #5940c1), la page de connexion, un footer sur mesure, le favicon, puis l’installation et la boucle d’itération.