15 — Theming: Tokens, SCSS, eigenes Theme
Für die Entwicklung. Wie die Gestaltung organisiert ist und wie ein Theme auf yavi_core aufgebaut wird.
Kein Build-Schritt
SCSS wird serverseitig von scssphp kompiliert, das mit Bootstrap Package kommt. Kein npm, kein webpack, kein dist/. SCSS bearbeiten, Cache leeren, neu laden:
ddev exec vendor/bin/typo3 cache:flushDie kompilierte Datei landet in typo3temp/assets/bootstrappackage/css/theme-<hash>.css. Der Hash leitet sich aus der Quelle ab; eine veraltete Datei wird also nie ausgeliefert — aber erst, nachdem der Cache geleert wurde.
Structure und Skin
Das SCSS des Core ist entlang einer Linie geteilt:
| Ordner | Enthält | Ein Theme … |
|---|---|---|
Scss/Structure/ | Geometrie, Layout, Verhalten — was eine Karte zur Karte macht | fasst es selten an |
Scss/Skin/ | Farbe, Schatten, Rahmen, Dekoration — was sie nach diesem Theme aussehen lässt | ersetzt es je Komponentenfamilie |
Ein Theme ersetzt die gesamte Skin-Datei einer Komponentenfamilie, statt einzelne Deklarationen zu überschreiben. Es gibt keine Cascade Layers; die Datei des Themes kommt schlicht später.
packages/yavi-lucerne/Resources/Public/Scss/
├── theme.scss Einstiegspunkt
├── _variables.scss die eigenen Tokens des Themes
├── _structure.scss generiert: die Liste der Structure-Partials des Core
├── _skin.scss generiert: die Liste der Skin-Partials, vom Core oder vom Theme
├── _custom.scss freier Raum für dieses Theme
└── Skin/ die Skin-Dateien, die dieses Theme besitzt
├── audioplayer.scss
├── buttons.scss
├── cards.scss
├── carousel.scss
├── cta.scss
└── menu.scsstheme.scss ist absichtlich kurz:
@import "EXT:yavi_core/Resources/Public/Scss/structure";
@import "variables";
@import "structure";
@import "skin";
@import "custom";Die Listen synchron halten
_structure.scss und _skin.scss sind generiert. Kommt im Core ein Partial hinzu, werden die Listen jedes Themes neu erzeugt:
ddev exec php packages/yavi-core/Build/sync-theme-scss.php
ddev exec php packages/yavi-core/Build/sync-theme-scss.php --check # schreibt nichts, scheitert bei AbweichungDie Variante --check läuft in der Testsuite; ein vergessenes Partial lässt also den Build scheitern, statt stillschweigend aus einem Theme zu verschwinden.
Das Token-System
Jeder konfigurierbare Wert ist eine CSS Custom Property mit genau einem Eigentümer.
// 1. yavi_core/_variables-defaults.scss — die theme-unabhängige Vorgabe
--card-radius: calc(12px * var(--theme-radius-scale, 1));
// 2. _variables.scss des Themes — der Wert dieses Themes
--card-radius: 25px;
// 3. Site Settings — vom ThemeCssVariablesRenderer in den Seitenkopf geschrieben
--card-radius: calc(18px * var(--theme-radius-scale, 1));Regeln, die daraus folgen:
- Vorgaben stehen im Core, Überschreibungen im Theme. Ein Theme deklariert nur, was abweicht — deklariert es aber vollständig, nie als „der andere Wert plus 2px“.
- Ein Token hat eine Bedeutung.
--card-radiusist der Stellknopf für Kartenecken; ein zweiter, der ebenfalls auf Karten wirkt, machte beide unzuverlässig. - Eingaben und Ausgaben sind getrennte Tokens. Wo ein Token sowohl konfiguriert als auch berechnet wird, heißt die Eingabe
…-baseund der aufgelöste Wert behält den schlichten Namen. Die Abstände arbeiten so: Gesetzt wird--section-spacing-md-base, gelesen wird--section-spacing-md.
Fallback-Ketten
Ein Token, das aus mehreren Ebenen überschrieben werden darf, liest sie in der Reihenfolge ihrer Autorität:
--megamenu-link-border-radius:
var(--megamenu-link-radius, /* Inline-Ueberschreibung - absolute px */
var(--nav-menu-radius, /* Backend-Feld */
calc(8px * var(--theme-radius-scale-mega, var(--theme-radius-scale, 1))))); /* Theme */Zu lesen als: Wer spezifischer ist, gewinnt, und ein nicht gesetztes Token fällt durch.
Erst literal, dann abgeleitet
Marken-Tokens werden zweimal deklariert:
--nav-link-active-bg: #{derive($secondary, 1.0674, 0.7915, 6.13)};
--nav-link-active-bg: oklch(from var(--theme-secondary) calc(l * 1.0674) …);Die erste Zeile ist eine statisch in SCSS berechnete Farbe für Browser ohne relative Farbsyntax; die zweite überschreibt sie dort, wo oklch(from …) unterstützt wird. Beide Zeilen werden gebraucht — die erste ist kein toter Code.
Ein neues Theme bauen
- Das Gerüst kopieren.
yavi_lucerneals Vorlage nehmen:composer.json,ext_emconf.php,Configuration/Sets/Full/,Resources/Public/Scss/. - Composer-Name und Extension Key setzen, z. B.
webagentur-yahya/yavi-zurich/yavi_zurich. - Das Set deklarieren:
``yaml # Configuration/Sets/Full/config.yaml name: yavi-zurich/full label: 'Yavi Zurich' dependencies: - yavi-core/full ``
- Einen eigenen Produktcode vergeben, in
Configuration/Sets/Full/settings.yaml:
``yaml licence: product: yavi-zurich ``
Wird das vergessen, scheitert die Lizenzprüfung genau wie bei einem falschen Schlüssel.
- Das Design in
settings.yamldeklarieren — Farben, Schriften, Höhen, Radien. Vollständig, nicht als Abweichung von Lucerne. - Die benötigten Skins übernehmen, in
Scss/Skin/, danachsync-theme-scss.phpausführen. - Die Navigation formen, über die Nav-Tokens plus
Skin/navigation.scss. Die acht Varianten selbst bleiben im Core: Sie werden über einen interpolierten Pfad eingebunden und lassen sich nicht je Theme ersetzen.
Templates überschreiben
Die Pfade für Templates, Partials und Layouts werden je Set konfiguriert. Ein Theme ergänzt eigene Pfade an einem höheren Index; TYPO3 fällt auf die Datei des Core zurück, wenn das Theme keine mitbringt. Nur die Datei kopieren, die tatsächlich geändert wird — jedes kopierte Template ist eines, das gepflegt werden muss, wenn sich das des Core ändert.
Wissenswertes, bevor eine Stunde in der Fehlersuche vergeht
- Eine CSS Custom Property wird an dem Element eingesetzt, das sie deklariert.
:root { --bs-card-border-radius: var(--card-radius) }wird einmal aufgelöst, an:root— eine Überschreibung von--card-radiusje Karte erreicht das nicht mehr. - Ein Verlaufs-Token kann kein Farbstopp innerhalb eines anderen Verlaufs sein. Es ist bereits ein vollständiger
linear-gradient(…)-Wert. - Bootstrap lässt sich über die Reihenfolge der Quellen überschreiben, da beide Regelsätze dieselbe Spezifität haben. Deshalb zählt die Reihenfolge der Imports in
theme.scss. - Der negative Rahmenabstand des Bootstrap Package frisst Abstand, sobald eine Utility-Klasse das
padding-topdes Rahmens überschreibt. Erst den Rahmen prüfen, bevor ein weiterer Abstand hinzukommt.