Eigene Alpine-Komponente bauen: State, Events, Interaktivität ohne separate JS-Datei
Eigene Alpine-Komponente bauen: State, Events, Interaktivität ohne separate JS-Datei
~8 Min. Lesezeit Zuletzt aktualisiert am 9. August 2026
Zum Abschluss von Block 4 bauen wir eine eigene, kleine Alpine-Komponente von Grund auf - einen einklappbaren "Akkordeon"-Bereich, wie man ihn zum Beispiel für FAQ-Abschnitte braucht. Das Muster ist bewusst so gewählt, dass es sich später leicht auf die Kategorie-Filterung der Team-Seite (Kapitel 21) übertragen lässt.
Schritt 1: den Zustand definieren
Zuerst überlegen wir, welcher Zustand überhaupt gebraucht wird. Für ein Akkordeon reicht eine einzige Information: welcher Eintrag (falls überhaupt einer) gerade geöffnet ist.
<div x-data="{ openIndex: null }">
<!-- Einträge folgen -->
</div>Schritt 2: die Einträge und die Toggle-Logik
<div x-data="{ openIndex: null }">
<?php foreach ($faqViewModel->getEntries() as $index => $entry): ?>
<div class="border-b border-slate-200">
<button
type="button"
@click="openIndex = openIndex === <?= (int) $index ?> ? null : <?= (int) $index ?>"
class="flex w-full items-center justify-between py-4 text-left font-semibold text-brand-dark"
>
<?= $escaper->escapeHtml($entry['question']) ?>
<span x-text="openIndex === <?= (int) $index ?> ? '−' : '+'"></span>
</button>
<p x-show="openIndex === <?= (int) $index ?>" x-cloak class="pb-4 text-brand-slate">
<?= $escaper->escapeHtml($entry['answer']) ?>
</p>
</div>
<?php endforeach; ?>
</div>Die Kernlogik steckt im @click-Ausdruck: Ist der geklickte Eintrag bereits offen, wird er geschlossen (null), sonst wird er zum neuen offenen Eintrag. So bleibt immer höchstens ein Eintrag gleichzeitig geöffnet.
Schritt 3: eine benannte Alpine.data()-Komponente statt Inline-Objekt
Für sehr einfache Fälle reicht das Inline-Objekt in x-data völlig aus. Wird die Logik komplexer (mehrere Methoden, wiederverwendbar an mehreren Stellen der Seite), lohnt sich eine benannte Komponente über Alpine.data() in einem Inline-<script>-Block - mit korrekter CSP-Registrierung:
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('faqAccordion', () => ({
openIndex: null,
toggle(index) {
this.openIndex = this.openIndex === index ? null : index;
},
isOpen(index) {
return this.openIndex === index;
},
}));
});
</script>
<?php $hyvaCsp->registerInlineScript(); ?>
<div x-data="faqAccordion">
<!-- @click="toggle(0)", x-show="isOpen(0)" usw. -->
</div>Der entscheidende Vorteil: toggle() und isOpen() sind lesbare Methodennamen statt eines langen Inline-Ausdrucks, und die Logik ist an einer Stelle gebündelt statt über mehrere x-data-Objekte im Template verstreut.
Tipp: Faustregel: ein einfaches Inline-Objekt in x-data="{ ... }", solange es nur ein oder zwei Zustandsvariablen und triviale Ausdrücke sind. Sobald echte Methoden mit mehreren Zeilen Logik nötig sind, lohnt sich Alpine.data().
Bereit für das durchgehende Projekt
Mit Layout-XML, ViewModels, Escaping, CSP, Tailwind und Alpine sind jetzt alle Bausteine vorhanden, die für eine eigene Storefront-Seite gebraucht werden. Ab Kapitel 17 setzen wir genau das in einem einzigen, durchgehenden Projekt zusammen: einer Team-Seite mit Kategorie-Filter.