ohne Knockout.js, ohne RequireJS, ohne UI-Components
Ein Hyvä-Theme folgt einer eigenen Verzeichnisstruktur und Bauweise: registration.php und theme.xml deklarieren das Paket, web/tailwind/ bringt den einzigen echten Build-Schritt, und Alpine.js läuft direkt im Template, ohne Compile-Step. Wer die Hyvä-Theme-Architektur einmal verstanden hat, findet sich in jedem Hyvä-Projekt sofort zurecht.
Inhaltsverzeichnis
- 1. Was ein Hyvä-Theme strukturell ist
- 2. Die Verzeichnisstruktur im Detail
- 3. Kein Build-Zwang: Tailwind statt Webpack
- 4. Composer-Abhängigkeit zum Parent-Theme
- 5. registration.php und theme.xml in der Praxis
- 6. web/tailwind/tailwind.config.js im Detail
- 7. etc/view.xml und Bildverarbeitung
- 8. Alpine.js Integration ohne Build-Step
- 9. Typische Anfängerfehler
- 10. Zusammenfassung
- 11. FAQ
1. Was ein Hyvä-Theme strukturell ist
Die Hyvä-Theme-Architektur unterscheidet sich fundamental von einem klassischen Luma-Theme, auch wenn beide auf demselben Magento-2-Themesystem mit Vererbung, Layout-XML und Area-Trennung aufbauen. Der entscheidende Unterschied liegt nicht in der Ordnerstruktur an sich, sondern darin, welche Frontend-Technologien innerhalb dieser Struktur überhaupt zum Einsatz kommen. Ein Luma-Theme bringt Knockout.js-Bindings, RequireJS-Module und ein UI-Component-Framework mit, das jede kleine Interaktion über mehrere Abstraktionsebenen aus JSON-Konfiguration, JS-Widgets und mixins.json zusammensetzt. Eine Hyvä-Theme-Architektur verzichtet bewusst auf all das.
Stattdessen setzt eine Hyvä-Theme-Architektur auf serverseitig gerenderte phtml-Templates, die mit Tailwind-Utility-Klassen gestylt sind und Interaktivität punktuell über Alpine.js erhalten. Es gibt keinen RequireJS-Modulgraphen, der beim Seitenaufruf aufgelöst werden muss, und keinen separaten Knockout-Template-Layer, der clientseitig neu rendert. Das phtml-Template ist gleichzeitig das fertige HTML, ergänzt um kleine Alpine.js-Direktiven direkt im Markup. Diese Reduktion auf Server-Rendering plus minimale clientseitige Interaktivität ist der eigentliche Kern jeder Hyvä-Theme-Architektur, nicht die Verzeichnisnamen, die weitgehend identisch zu Luma bleiben.
| Aspekt | Luma (klassisch) | Hyvä-Theme-Architektur | Vorteil |
|---|---|---|---|
| JS-Framework | Knockout.js + RequireJS | Alpine.js, kein Bundler | Kein Modulgraph, kleinere Payload |
| CSS-Stack | LESS + eigenes Grid-System | Tailwind CSS v4, Utility-first | Kein ungenutztes CSS im Bundle |
| Template-Layer | phtml + Knockout-Templates (.html) | Ausschließlich phtml | Ein Rendering-Layer statt zwei |
| Build-Pflicht | grunt/webpack für JS und CSS | Nur npm run build für Tailwind | Kein JS-Compile-Schritt |
| Interaktivität | UI-Component-Widgets, data-mage-init | x-data direkt im Template | Lesbar ohne Konfigurationsebene |
Die Tabelle zeigt, warum ein Wechsel von Luma zu Hyvä kein reines Facelifting ist, sondern eine architektonische Entscheidung. Wer ein neues Theme aufsetzt und dabei UI-Component-Layout-XML aus einem Luma-Modul kopiert, baut unbewusst wieder Abhängigkeiten auf, die eine Hyvä-Theme-Architektur gerade vermeiden soll. Die folgenden Abschnitte zeigen, wie sich dieser architektonische Unterschied konkret in Dateien, Ordnern und Composer-Abhängigkeiten niederschlägt.
2. Die Verzeichnisstruktur im Detail
Wer ein neues Theme unter app/design/frontend/Vendor/theme-name/ anlegt, sieht auf den ersten Blick eine vertraute Magento-Struktur. Trotzdem hat die Hyvä-Theme-Architektur einige Verzeichnisse, die in einem klassischen Theme entweder fehlen oder eine andere Rolle spielen. Ganz oben liegt registration.php, das Theme bei Magento als Komponente registriert. Direkt daneben steht theme.xml, das den Theme-Namen, das Parent-Theme und optional ein Vorschaubild deklariert.
Darunter folgt composer.json, das aus dem Theme-Ordner ein installierbares Composer-Paket macht, mit einer expliziten Abhängigkeit zum Hyvä-Parent-Theme. Das Verzeichnis web/tailwind/ ist das auffälligste Merkmal einer Hyvä-Theme-Architektur: Hier liegen tailwind.config.js, tailwind-source.css und die node_modules-Abhängigkeiten für den CSS-Build. Templates gliedern sich wie gewohnt nach Modul: Magento_Theme/templates für Header, Footer und Root-Template, Magento_Catalog/templates für Produktlisten und Produktdetailseiten, Magento_Checkout/templates für den Checkout und so weiter, jedes Modul-Override folgt exakt dem Namespace des Kernmoduls.
Zusätzlich enthält etc/view.xml die Bildgrößen-Konfiguration, etc/di.xml theme-spezifische Dependency-Injection-Anpassungen, und Magento_Theme/layout/default.xml die grundlegende Seitenstruktur mit Blöcken für Header, Footer und Breadcrumbs. Eine typische minimale Hyvä-Theme-Architektur braucht nicht mehr als registration.php, theme.xml, composer.json und den web/tailwind/-Ordner, um lauffähig zu sein, alles Weitere sind Overrides bestehender Templates aus dem Hyvä-Default-Theme.
3. Kein Build-Zwang: Tailwind statt Webpack
Der größte praktische Unterschied zwischen Luma und einer Hyvä-Theme-Architektur zeigt sich beim täglichen Entwickeln: Bei Luma erzwingt RequireJS mit seiner Modul-Konfiguration und dem optionalen grunt- oder webpack-Build einen zusätzlichen Kompilierschritt, sobald JavaScript-Module verändert werden. Eine Hyvä-Theme-Architektur kennt diesen Zwang für JavaScript nicht. Alpine.js wird als einzelnes, unminifiziertes Script eingebunden und direkt im Browser interpretiert, ganz ohne Transpiling, ohne Bundling, ohne Babel-Schritt.
Der einzige echte Build-Schritt in einer Hyvä-Theme-Architektur betrifft CSS: Tailwind CSS muss aus den Utility-Klassen im Template-Code ein optimiertes Stylesheet generieren. Das geschieht über npm run build im web/tailwind/-Verzeichnis, das intern den Tailwind-CLI-Compiler aufruft. Dieser Schritt scannt alle konfigurierten Template-Pfade nach verwendeten Klassennamen und erzeugt daraus eine einzige, produktionsreife CSS-Datei mit ausschließlich den tatsächlich genutzten Utility-Klassen.
Für die tägliche Entwicklung bedeutet das: Wer nur JavaScript-Verhalten mit Alpine.js ändert, muss überhaupt nichts bauen, ein einfacher Browser-Reload genügt. Wer neue Tailwind-Klassen im Template verwendet, muss den Tailwind-Watcher laufen lassen oder npm run build manuell ausführen, damit die neuen Klassen im generierten CSS landen. Diese Trennung zwischen "kein Build nötig" für JavaScript und "ein CSS-Build nötig" für Tailwind ist einer der am häufigsten missverstandenen Aspekte der Hyvä-Theme-Architektur, besonders bei Entwicklern, die von Luma mit seinem RequireJS-Bundling kommen.
4. Composer-Abhängigkeit zum Parent-Theme
Jede Hyvä-Theme-Architektur baut auf einem Parent-Theme auf, in den allermeisten Projekten ist das hyva-themes/magento2-default-theme-csp. Dieses Parent-Theme liefert sämtliche Basis-Templates, das Tailwind-Grundsetup und die CSP-konforme Alpine.js-Integration mit. Ein eigenes Theme deklariert diese Abhängigkeit gleich zweimal: einmal in theme.xml über das parent-Element, und einmal in composer.json als reguläre Composer-Requirement.
Die Composer-Abhängigkeit sorgt dafür, dass das Parent-Theme als eigenständiges Paket über Composer verwaltet wird, mit fester Versionsangabe statt eines manuell heruntergeladenen Ordners. Bei einem Update des Parent-Themes reicht composer update hyva-themes/magento2-default-theme-csp, um die neue Version einzuspielen, ohne den eigenen Theme-Code anzufassen. Wie genau Magento anschließend beim Rendern eines Templates zwischen Kind- und Parent-Theme entscheidet, welche Datei greift, ist ein eigener Mechanismus für sich und Thema eines separaten Artikels zum Template-Fallback.
{
"name": "mironsoft/theme-frontend-default",
"description": "Mironsoft custom Hyva theme based on the Hyva default theme",
"type": "magento2-theme",
"version": "1.0.0",
"license": "OSL-3.0",
"require": {
"php": "~8.4.0",
"hyva-themes/magento2-default-theme-csp": "^1.3",
"hyva-themes/magento2-theme-fallback": "^1.1"
},
"autoload": {
"files": [
"registration.php"
]
}
}
5. registration.php und theme.xml in der Praxis
registration.php ist die kürzeste, aber unverzichtbarste Datei jeder Hyvä-Theme-Architektur. Sie meldet das Theme über ComponentRegistrar::register() mit dem Typ THEME und einem eindeutigen Bezeichner im Format Area/Vendor/theme-name bei Magento an. Ohne diese Registrierung erscheint das Theme nicht in der Admin-Theme-Liste und kann keinem Store zugewiesen werden.
<?php
/**
* Hyva child theme registration.
*/
declare(strict_types=1);
use Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(
ComponentRegistrar::THEME,
'frontend/Mironsoft/default',
__DIR__
);
theme.xml ergänzt die reine Registrierung um Metadaten: den sprechenden Titel, der im Adminbereich angezeigt wird, und vor allem das parent-Element, das die Vererbungskette zum Hyvä-Basistheme herstellt. Fehlt dieses Element oder zeigt es auf ein falsches Parent-Theme, greift der Template-Fallback nicht wie erwartet und viele Basis-Templates aus dem Hyvä-Default-Theme werden schlicht nicht gefunden. Für eine solide Hyvä-Theme-Architektur ist ein korrekt gesetzter Parent in theme.xml daher eine der ersten Prüfungen nach dem Anlegen eines neuen Themes.
<?xml version="1.0" encoding="UTF-8"?>
<theme xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Config/etc/theme.xsd">
<title>Mironsoft Default (Hyva)</title>
<parent>Hyva/default</parent>
<media>
<preview_image>media/preview.jpg</preview_image>
</media>
</theme>
Beide Dateien zusammen, registration.php und theme.xml, bilden das absolute Minimum, das Magento benötigt, um ein Verzeichnis überhaupt als Theme zu erkennen. Alles andere in der Hyvä-Theme-Architektur, von der Tailwind-Konfiguration bis zu den Template-Overrides, baut auf dieser Basis auf.
6. web/tailwind/tailwind.config.js im Detail
Das Herzstück des CSS-Build-Schritts in jeder Hyvä-Theme-Architektur ist web/tailwind/tailwind.config.js. Diese Datei legt über das content-Array fest, welche Dateien Tailwind nach verwendeten Klassennamen durchsucht. Anders als bei einem klassischen JavaScript-Framework mit Komponentenbaum durchsucht Tailwind hier direkt die .phtml-Dateien im gesamten Theme-Verzeichnisbaum, inklusive aller Parent-Themes und aller aktiven Module, deren Templates im Theme überschrieben werden könnten.
Diese direkte Analyse der phtml-Dateien ist möglich, weil eine Hyvä-Theme-Architektur keine JSX-Komponenten oder Template-Strings in JavaScript-Dateien verwendet, sondern reines serverseitiges HTML mit eingestreuten PHP-Ausdrücken. Tailwind muss also keinen JavaScript-AST parsen, um Klassennamen zu extrahieren, ein einfacher Textscan über die phtml-Dateien reicht aus. Auch wenn Tailwind CSS v4 grundsätzlich einen CSS-first-Ansatz mit automatischer Inhaltserkennung verfolgt, behält eine Hyvä-Theme-Architektur die explizite content-Konfiguration bei, weil Templates aus mehreren Parent-Theme-Ebenen und Vendor-Modulen gescannt werden müssen, die eine automatische Erkennung nicht zuverlässig abdeckt. Fehlt ein Pfad im content-Array, etwa weil ein neues Modul-Verzeichnis nicht ergänzt wurde, werden dessen Klassen beim Build stillschweigend nicht erkannt.
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'../../../../**/Magento_*/templates/**/*.phtml',
'../../../../**/*/templates/**/*.phtml',
'./**/*.phtml',
'../Magento_Theme/templates/**/*.phtml',
'../../../vendor/hyva-themes/**/templates/**/*.phtml'
],
theme: {
extend: {
colors: {
primary: {
DEFAULT: '#5c1a2e',
light: '#b3294f'
}
}
}
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography')
]
}
Neben dem content-Array definiert die Konfiguration üblicherweise Theme-Erweiterungen wie eigene Farben, Schriftgrößen oder Breakpoints unter theme.extend, sowie Tailwind-Plugins wie @tailwindcss/forms oder @tailwindcss/typography, die für die prose-Klassen in Blog- und CMS-Inhalten benötigt werden. Die begleitende tailwind-source.css importiert die Tailwind-Layer und bindet zusätzliche Basis-Styles oder Custom-Utilities ein, bevor der Build-Prozess daraus die finale styles.css generiert.
7. etc/view.xml und Bildverarbeitung
etc/view.xml ist in einer Hyvä-Theme-Architektur weiterhin für die Bildverarbeitung zuständig, genau wie in einem klassischen Luma-Theme. Hier werden für jeden Bildtyp, etwa category_page_grid oder product_page_image_large, feste Breiten und Höhen sowie das Skalierungsverhalten definiert. Magento generiert daraus beim ersten Aufruf automatisch die passenden gecachten Bildvarianten im Verzeichnis pub/media/catalog/product/cache/.
Der Unterschied zu Luma liegt weniger in der Struktur von view.xml selbst als darin, wie konsequent eine Hyvä-Theme-Architektur auf natives, verzögertes Laden von Bildern setzt: Templates aus dem Hyvä-Default-Theme nutzen durchgängig loading="lazy" und geben Breite sowie Höhe explizit an, um Cumulative-Layout-Shift zu vermeiden, ganz ohne ein zusätzliches JavaScript-Lazy-Loading-Polyfill, wie es in älteren Luma-Setups üblich war. Auch responsive Bildgrößen werden über mehrere in view.xml deklarierte Varianten und das srcset-Attribut im Template gelöst, nicht über ein serverseitiges Bild-Resizing-Modul zur Laufzeit.
Wichtig für die Praxis: Änderungen an view.xml greifen erst nach bin/magento catalog:images:resize beziehungsweise nach dem Neu-Generieren der Bild-Caches. Wer neue Bildtypen in einer Hyvä-Theme-Architektur einführt, etwa für eine neue Produktkachel-Variante, sollte diesen Schritt fest in die Deploy-Routine aufnehmen, da sonst alte Bildgrößen im Cache verbleiben und im Frontend sichtbar falsch skaliert erscheinen.
8. Alpine.js Integration ohne Build-Step
Die vermutlich sichtbarste Eigenschaft einer Hyvä-Theme-Architektur im Template-Code ist die direkte Verwendung von Alpine.js-Direktiven wie x-data, x-show und @click innerhalb der phtml-Datei, ganz ohne separate JavaScript-Datei und ohne Build-Schritt. Der Zustand einer Komponente, etwa ob ein Dropdown geöffnet ist, wird als einfaches JavaScript-Objekt direkt im x-data-Attribut deklariert und ist damit unmittelbar neben dem Markup sichtbar, das er steuert.
Diese Kolokation von Zustand und Markup ist ein bewusster Architekturentscheid: Statt eine Komponente in eine separate .js-Datei auszulagern und über einen Modulnamen im Template zu referenzieren, wie es RequireJS-basierte UI-Components in Luma tun, bleibt in einer Hyvä-Theme-Architektur alles an einer Stelle lesbar. Für komplexere, wiederverwendbare Komponenten lassen sich Alpine-Datenfunktionen dennoch in Magento_Theme/web/js/ auslagern und per Alpine.data() registrieren, auch das läuft ohne Compile-Schritt, da Alpine.js selbst unminifiziert im Browser interpretiert wird.
<?php /** @var \Magento\Framework\View\Element\Template $block */ ?>
<div x-data="{ open: false }" class="relative inline-block">
<button
type="button"
@click="open = !open"
class="flex items-center gap-2 px-4 py-2 text-sm font-semibold text-gray-700"
>
<span>Sortieren</span>
<svg class="w-4 h-4" :class="{ 'rotate-180': open }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</button>
<ul
x-show="open"
x-cloak
@click.outside="open = false"
class="absolute right-0 mt-2 w-48 bg-white border border-gray-200 rounded-lg shadow-lg z-10"
>
<li><a class="block px-4 py-2 text-sm hover:bg-gray-50" href="?product_list_order=price">Preis aufsteigend</a></li>
<li><a class="block px-4 py-2 text-sm hover:bg-gray-50" href="?product_list_order=name">Name A-Z</a></li>
</ul>
</div>
Wichtig im CSP-Kontext: Jeder Inline-<script>-Block muss über den ViewModel $hyvaCsp mit registerInlineScript() registriert werden, sonst blockt die Content-Security-Policy des CSP-Themes das Skript im Browser. Für reine x-data-Attribute im Markup ist das nicht nötig, da Alpine diese Attribute liest, ohne dass ein zusätzliches Inline-Script ausgeführt werden muss, das nicht bereits über die zentrale Alpine.js-Datei abgedeckt ist.
9. Typische Anfängerfehler beim Aufsetzen eines neuen Hyvä-Themes
Der häufigste Fehler beim Aufsetzen einer neuen Hyvä-Theme-Architektur ist eine fehlende oder falsche Parent-Theme-Deklaration in theme.xml. Ohne korrektes parent-Element erbt das eigene Theme keine der Basis-Templates aus hyva-themes/magento2-default-theme-csp, Magento wirft dann für scheinbar triviale Templates wie den Header einen "template not found"-Fehler, obwohl der eigene Code an sich korrekt ist.
Der zweite typische Fehler betrifft den content-Pfad in tailwind.config.js: Zeigt der Pfad nur auf das eigene Theme-Verzeichnis, aber nicht auf die vererbten Templates aus dem Parent-Theme oder auf Custom-Module unter app/code/, fehlen genau die Tailwind-Klassen im generierten CSS, die dort verwendet werden. Das Symptom ist meist ein Layout, das im Grundgerüst funktioniert, bei dem aber einzelne Komponenten unstyled oder falsch positioniert erscheinen, ohne dass eine Fehlermeldung auf die Ursache hinweist.
Der dritte häufige Fehler in einer neu aufgesetzten Hyvä-Theme-Architektur sind CSP-Verletzungen durch Inline-Scripts, die nicht über $hyvaCsp->registerInlineScript() registriert wurden. Das CSP-Theme blockt jedes Inline-Script, dessen Hash nicht in der Content-Security-Policy hinterlegt ist, was in der Browser-Konsole als stiller CSP-Fehler erscheint und leicht übersehen wird, wenn niemand aktiv die Konsole prüft. Wer ein neues Theme aufsetzt, sollte diese drei Punkte, Parent-Theme, Tailwind-content-Pfade und CSP-Registrierung, als erste Checkliste abarbeiten, bevor tiefer in die Fehlersuche eingestiegen wird.
10. Zusammenfassung
Die Hyvä-Theme-Architektur ersetzt die mehrschichtige Struktur aus Knockout.js, RequireJS und UI-Components durch serverseitig gerenderte phtml-Templates, Tailwind-Utility-Klassen und punktuelle Alpine.js-Interaktivität. registration.php und theme.xml bilden das strukturelle Minimum, composer.json bindet das Hyvä-Parent-Theme als versioniertes Paket ein, und web/tailwind/ enthält den einzigen echten Build-Schritt des gesamten Themes.
Wer eine neue Hyvä-Theme-Architektur aufsetzt, profitiert davon, dass JavaScript ohne Compile-Schritt läuft, Tailwind gezielt nur die tatsächlich verwendeten Klassen ausliefert, und etc/view.xml die Bildverarbeitung unabhängig vom restlichen Frontend-Stack regelt. Die typischen Anfängerfehler, fehlendes Parent-Theme, falscher Tailwind-content-Pfad und ungesicherte Inline-Scripts, lassen sich mit der richtigen Checkliste vermeiden, bevor sie im Live-Betrieb sichtbar werden.
Hyvä-Theme-Architektur, das Wichtigste auf einen Blick
Kein JS-Build-Zwang
Alpine.js läuft ohne Compile-Step direkt im Browser. Nur Tailwind CSS benötigt einen echten Build via npm run build.
Minimales Grundgerüst
registration.php und theme.xml reichen, um ein Theme zu registrieren und vererbbar zu machen.
Composer-Parent-Theme
hyva-themes/magento2-default-theme-csp als versionierte Abhängigkeit in composer.json und theme.xml.
Häufigste Fehler
Fehlender Parent in theme.xml, unvollständiger Tailwind-content-Pfad, fehlende hyvaCsp-Registrierung von Inline-Scripts.
11. FAQ: Hyvä-Theme-Architektur
1Was unterscheidet eine Hyvä-Theme-Architektur von Luma?
2Welche Dateien sind mindestens nötig?
3Braucht Alpine.js einen Build-Schritt?
4Warum braucht Tailwind trotzdem einen Build?
5Wo trage ich das Parent-Theme ein?
6Was macht composer.json im Theme?
7Wofür ist etc/view.xml zuständig?
8Häufigster Fehler im Tailwind-content-Pfad?
9Warum blockiert die CSP mein Inline-Script?
10Kann ich Magento_Catalog/templates überschreiben?
Mironsoft
Hyvä-Theme-Entwicklung, Tailwind CSS und Alpine.js für Magento 2
Eine solide Hyvä-Theme-Architektur für euren Magento-2-Shop?
Wir bauen neue Hyvä-Themes von Grund auf, migrieren bestehende Luma-Shops und richten Tailwind-Build sowie CSP-konforme Alpine.js-Integration sauber nach Best Practices ein.
Theme-Setup
Neue Hyvä-Theme-Architektur mit registration.php, theme.xml und Composer-Parent-Theme sauber aufsetzen
Tailwind & Alpine.js
Content-Pfade, Build-Pipeline und Alpine-Komponenten ohne CSP-Verstöße einrichten
Theme-Audit
Bestehende Hyvä-Themes auf Anfängerfehler, Performance und Wartbarkeit prüfen