Skip to Content

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.php du 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, themedesignermode dé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.php de 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 :

ChampRôle
nameidentité, = dossier
parentshéritage (Boost) — le champ le plus important
scssle SCSS principal du thème (souvent : « prends Boost + mon preset »)
prescsscallbackinjecte des variables SCSS avant le preset (surcharge Bootstrap)
extrascsscallbackinjecte du SCSS après le preset (surcharges finales)
layoutsmappage type-de-page → fichier de layout (hérité si vide)
rendererfactoryactive la surcharge des renderers PHP
iconsystemFontAwesome 6

💡 Pour un dev React : config.php est le theme.config de 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 $primary dans 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 pas themedesignermode.

⚠️ 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. themedesignermode est 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) :

NiveauExemple d’usageActivé par
URLforcer un thème via ?theme= (dev/test)$CFG->allowthemechangeonurl
Utilisateurun thème par utilisateur$CFG->allowuserthemes
Cohorteun thème par groupe d’utilisateursréglage cohorte
Coursthème propre à un cours$CFG->allowcoursethemes
Catégoriethème par catégorie de cours$CFG->allowcategorythemes
Sitele thème par défautApparence → 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

Il peut modifier 100 % du CSS, du HTML (templates) et du JS de l’interface. Il ne doit jamais modifier le code du core (« core hack ») : tout passe par le thème (SCSS, surcharges de templates/renderers/layouts), qui hérite de Boost.

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

1. pre-SCSS (avant) : les variables Bootstrap à surcharger ($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

Compilé côté serveur par scssphp (pas de Node requis) et mis en cache. En dev, on active 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

Niveaux (du plus spécifique au plus général) : URL, utilisateur, cohorte, cours, catégorie, site. La plupart des sites n’utilisent que le niveau site. Les niveaux catégorie/cours/cohorte servent au multi-marque (une plateforme, plusieurs identités visuelles). Les device themes sont vestigiaux (l’app mobile a son propre rendu).

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.