Flexibler Tailwind-Grid-Pagebuilder für REDAXO 5. Ermöglicht das visuelle Aufbauen von Layouts aus Zeilen, Spalten und beliebigen REDAXO-Modulen — direkt im Backend. Inspiriert von gridblock — vielen Dank an die Entwickler!
Jede Änderung am Addon muss auf einer Live-Seite gefahrlos einspielbar sein — bestehende Inhalte dürfen sich weder verändern noch kaputtgehen.
Verbindliche Regeln für jede Weiterentwicklung:
- Neue Felder immer additiv einführen und in
module/output.phpmit Default absichern ($obj['neu'] ?? 0). Fehlt ein Feld in alten Daten (JSON inrex_article_slice), muss das exakte bisherige Verhalten herauskommen. - Umbenannte/ersetzte Felder brauchen eine Migration in
assets/tw-gridbuilder.js(migrate*-Funktionen), die alte Werte verlustfrei und visuell identisch überführt (Beispiel: altesrounded:true→ alle Eckenlg, entspricht dem früherenrounded-lg). - Vor jedem Release mit echten Alt-Daten testen: alte Seite öffnen, Slice ohne die neuen Felder rendern → Ausgabe muss unverändert sein.
- Wenn eine Änderung bestehende Angaben doch beeinflussen könnte: nicht still einbauen — im CHANGELOG unter „
⚠️ Breaking / Achtung" klar dokumentieren und den Betreiber aktiv darauf hinweisen. - Nach dem Addon-Update muss das Modul aktualisiert werden (Modul-Code aus
module/output.php+module/input.phpin die REDAXO-Modul-Definition übernehmen), sonst greift der neue Output nicht.
Setzt einen Tailwind-v4-Build voraus
TW GridBuilder generiert Tailwind-Utility-Klassen (Grid, Spacing, Container, Hintergrundfarben). Ab Version 2.2.0 werden diese Klassen vom Tailwind-Build des Projekts erzeugt (Farben stammen aus den Theme-Variablen, Responsive-Varianten funktionieren korrekt), nicht mehr als vorkompilierte, unlayered Utilities mitgeliefert. tw-gridbuilder-grid.css enthält dazu eine Tailwind-@source inline(...)-Safelist plus die wenigen echten Custom-Klassen (Video-Hintergrund, Mobile-Reihenfolge). Voraussetzung ist daher, dass die Datei per @import in einen Tailwind-v4-Build eingebunden ist — siehe „Grid-CSS in den Build-Prozess einbinden".
Hintergrund des Umbaus: Die früher mitgelieferten generischen Utilities (
.flex,.gap-*,.container…) waren unlayered CSS und überschrieben in Tailwind-Projekten die@layer-Utilities — dadurch griffen Responsive-Varianten wielg:hiddennicht mehr. Der Safelist-Ansatz beseitigt diese Kollision.
| Paket | Mindestversion | Zweck |
|---|---|---|
| REDAXO | 5.15 | Core |
| PHP | 8.1 | Sprachfeatures |
TW GridBuilder selbst hat keine harte Abhängigkeit zu MForm oder Focuspoint — beide werden nur genutzt, wenn sie aktiv sind, und degradieren sonst sauber:
| Paket | Zweck | Verhalten wenn nicht aktiv |
|---|---|---|
| MForm | Wird von den Inhaltsmodulen, die in den Zellen platziert werden, für deren eigene Formularfelder genutzt — nicht von tw_gridbuilder selbst | Kein Einfluss auf tw_gridbuilder; nur relevant, falls einzelne Inhaltsmodule MForm voraussetzen |
| Focuspoint | Bildausschnitt für Hintergrundbilder (Zeilen & Zellen) | Fallback auf background-position: 50% 50% (Bild-Center), keine Fehler |
| animate.css + alpinejs-intersect-class | Voraussetzung für die Animationen (Zeilen & Zellen). Wird nur benötigt, wenn im Panel eine Animation ausgewählt ist | Ohne animate.css/Plugin passiert nichts — es wird lediglich das Attribut x-intersect-class.once="animate__animated …" ausgegeben, das ohne die Bibliothek wirkungslos bleibt (keine Fehler, keine Darstellungsänderung) |
tw_gridbuilder selbst rendert reines HTML/CSS (Grid, Spacing, Container) — für das Grundlayout ist kein JavaScript nötig.
Ausnahme Animationen: Wählt man im Panel für eine Zeile oder Zelle eine Animation, gibt das Modul das Alpine-Attribut x-data x-intersect-class.once="animate__animated …" aus. Das setzt im Theme zwei Dinge voraus:
- animate.css (die
animate__*-Klassen) — z.B. via npm oder CDN eingebunden. - alpinejs-intersect-class als Alpine-Plugin registriert (
Alpine.plugin(intersectClass)) — löst die Animation einmalig aus, sobald das Element ins Sichtfeld scrollt.
Fehlt eines von beiden, bleibt das Attribut folgenlos — das Grid rendert normal, nur ohne Animation.
Tailwind bzw. Alpine.js sind darüber hinaus nur relevant, falls einzelne Inhaltsmodule in den Zellen sie voraussetzen — das hängt vom jeweiligen Modul ab, nicht von tw_gridbuilder.
| Datei | Zweck |
|---|---|
assets/tw-gridbuilder.js |
Vue 3 (via REDAXO-Backend-CDN), gesamte Pagebuilder-UI |
assets/tw-gridbuilder.css |
Backend-Styles für den Editor |
assets/tw-gridbuilder-grid.css |
Frontend-CSS: Tailwind-@source-Safelist der dynamischen Utilities + Custom-Klassen (Video-Hintergrund, Mobile-Reihenfolge). Muss per @import in den Tailwind-v4-Build eingebunden werden — siehe „Grid-CSS in den Build-Prozess einbinden" |
- Addon in
redaxo/src/addons/tw_gridbuilder/ablegen - Im REDAXO-Backend unter AddOns → tw_gridbuilder installieren und aktivieren
- Neues Modul anlegen:
- Input: Inhalt aus
module/input.phpeinfügen - Output: Inhalt aus
module/output.phpeinfügen
- Input: Inhalt aus
- Modul einer Seite/Template zuweisen und im Slice verwenden
- Eigene Inhaltsmodule mit dem Kommentar
/* tw_gridblock kompatibel */im Input markieren — nur diese erscheinen in der Modulauswahl des Pagebuilders
tw_gridbuilder/
├── assets/
│ ├── tw-gridbuilder.js # Backend-UI (Vue 3)
│ ├── tw-gridbuilder.css # Backend-Styles
│ └── tw-gridbuilder-grid.css # Safelist (@source) + Custom-CSS; benötigt Tailwind-v4-Build
├── lib/
│ ├── TwGridBuilderApi.php # AJAX-API: Modul-Formulare & Previews
│ └── TwGridBuilderHelper.php # REX_VALUE/REX_MEDIA Token-Auflösung
├── module/
│ ├── input.php # Modul-Input (Kopiervorlage)
│ └── output.php # Modul-Output (Kopiervorlage)
├── boot.php # Addon-Bootstrap
├── package.yml # Metadaten & Abhängigkeiten
├── composer.json # PSR-4-Autoload (FriendsOfRedaxo\TwGridBuilder\)
├── CHANGELOG.md # Versionshistorie
├── LICENSE.md # Lizenz
└── README.md # Diese Datei
Das gesamte Layout wird als JSON in REX_VALUE[1] des Slices gespeichert:
{
"rows": [
{
"id": "...",
"container": "standard",
"content_width": "standard",
"bg": "",
"bg_image": "",
"bg_video": "",
"py_top": "3",
"py_bottom": "3",
"gap": "4",
"text_align": "",
"mobile_reverse": false,
"cells": [
{
"id": "...",
"span": 6,
"bg": "",
"bg_image": "",
"bg_video": "",
"py_top": "0",
"py_bottom": "0",
"px": "0",
"align": "start",
"text_align": "",
"rounded": false,
"modules": [
{
"id": "...",
"module_id": 5,
"values": {
"1": "Überschrift",
"__media_1": "bild.jpg"
}
}
]
}
]
}
]
}Formularfelder innerhalb einer Zelle werden umbenannt um Konflikte zu vermeiden:
REX_INPUT_VALUE[n]→REX_INPUT_VALUE[twgb][{cell_id}][n]REX_INPUT_MEDIA[n]→REX_INPUT_VALUE[twgb][{cell_id}][__media_n]
Alle Abstände (Zeile: oben/unten/Gap, Zelle: oben/unten/innen) sind pro Breakpoint einstellbar:
| Breakpoint | Klassen-Präfix | Bereich |
|---|---|---|
| Smartphone (alle Größen) | pt-, pb-, px-, gap- |
Basiswert |
| Tablet (ab 768px) | md:pt-, md:pb-, … |
überschreibt Smartphone |
| Desktop (ab 1024px) | lg:pt-, lg:pb-, … |
überschreibt Tablet |
Tablet- und Desktop-Klassen werden nur ausgegeben wenn sie vom jeweils kleineren Breakpoint abweichen.
- Bilder:
style="background-image: url(...);"direkt auf dem Element — kein z-index nötig - Videos:
relative z-10auf dem Container +.video-docker-Div - Focuspoint: wird automatisch ausgelesen wenn das Addon aktiv ist
Nur Module mit dem Kommentar /* tw_gridblock kompatibel */ irgendwo im Input-Code erscheinen in der Modulauswahl. So lässt sich gezielt steuern welche Module im Pagebuilder nutzbar sind.
Jede Zeile und jede Zelle bekommt im Frontend zusätzlich zu den Layout-Utility-Klassen (grid-cols-12, col-span-4 etc.) feste, sprechende Klassen, die das Spalten-Layout beschreiben — unabhängig von Farbe, Abstand oder Inhalt. Damit lassen sich Layouts gezielt per CSS ansprechen (z.B. ein individuelles Design nur für eine bestimmte Spaltenaufteilung), ohne jede Zeile einzeln im Backend markieren zu müssen.
Zeilen-Wrapper:
| Klasse | Bedeutung |
|---|---|
twgb-row |
Kennzeichnet jeden Zeilen-Wrapper generell |
twgb-row--cols-{n} |
Anzahl der Zellen in der Zeile, z.B. twgb-row--cols-2 bei zwei Spalten |
twgb-row--span-{a}-{b}-... |
Span-Verhältnis der Zellen in Reihenfolge (auf Basis des 12er-Grids), z.B. twgb-row--span-4-8 bei einer 1/3+2/3-Aufteilung, twgb-row--span-6-3-3 bei 1/2+1/4+1/4 |
Zellen:
| Klasse | Bedeutung |
|---|---|
twgb-cell |
Kennzeichnet jede Zelle generell |
twgb-cell--{i}-of-{n} |
Position der Zelle in der Zeile, 1-basiert, z.B. twgb-cell--1-of-2, twgb-cell--2-of-2 |
twgb-cell--span-{n} |
Eigener Span-Wert dieser Zelle (0–12), z.B. twgb-cell--span-4 |
Beispiel — zwei Spalten mit 1/3 + 2/3 (Span 4 + 8):
<div class="grid grid-cols-12 gap-4 twgb-row twgb-row--cols-2 twgb-row--span-4-8">
<div class="col-span-4 twgb-cell twgb-cell--1-of-2 twgb-cell--span-4">…</div>
<div class="col-span-8 twgb-cell twgb-cell--2-of-2 twgb-cell--span-8">…</div>
</div>Warum Span-Zahlen statt Brüche (z.B. 1_3/2_3)? Intern wird jede Spaltenbreite ohnehin als Span-Wert auf Basis von 12 Spalten gespeichert (col-span-{n}). Eine Umrechnung in gekürzte Brüche (⅓, ⅔, ½ …) wäre verlustbehaftet und mehrdeutig — z.B. sähen span-6 und eine andere, tatsächlich abweichende Aufteilung nach dem Kürzen identisch aus. Die rohen Span-Zahlen sind eindeutig und brauchen keine Umrechnung.
Warum keine IDs? IDs müssen pro Seite eindeutig sein. Zwei Zeilen mit identischem Layout (z.B. zwei 1/3+2/3-Zeilen) hätten sonst doppelte IDs — ungültiges HTML und kaputte #id-Selektoren/getElementById-Aufrufe. Klassen können beliebig oft vorkommen und sind daher der richtige Mechanismus für einen wiederkehrenden Struktur-Descriptor. Für echte Eindeutigkeit (z.B. Sprungmarken oder JS-Hooks pro einzelner Zeile/Zelle) existiert bereits eine id pro Row/Cell im gespeicherten JSON (siehe „Datenspeicherung" oben) — die lässt sich bei Bedarf zusätzlich als HTML-id ausgeben.
Jede Zeile lässt sich am Griff ⠿ im Zeilen-Header greifen und per Drag & Drop an eine andere Position schieben. Alternativ verschieben die Pfeil-Buttons ↑ / ↓ im Zeilen-Header die Zeile um eine Position.
Eine Spalte lässt sich am Griff ⠿ in der Zelle (oder an der Zelle selbst) greifen und ablegen:
- innerhalb derselben Zeile — die Spaltenreihenfolge ändert sich
- in einer beliebigen anderen Zeile — die Spalte wechselt samt allen enthaltenen Modulen und allen Zell-Einstellungen die Zeile
Beim Ziehen zeigt eine blaue Einfügemarke die Zielposition an. Ob die Spalte vor oder hinter der Zelle landet, über der der Mauszeiger steht, entscheidet die linke bzw. rechte Hälfte dieser Zelle. Wird über der Freifläche einer Zeile losgelassen, hängt die Spalte hinten an — so lässt sich auch in eine leere Zeile ablegen. Die Zielzeile wird beim zeilenübergreifenden Ziehen farblich hervorgehoben, die gezogene Spalte transparent dargestellt.
Ist die verschobene Spalte gerade im Panel geöffnet, bleibt sie geöffnet und ist danach korrekt der neuen Zeile zugeordnet.
Die Spaltenbreite (
span) wandert unverändert mit und wird nicht automatisch an die Zielzeile angepasst. Eine Zeile kann dadurch vorübergehend mehr oder weniger als 12 Spalten belegen — im Backend rendert das proportional weiter, im Frontend bricht die Zeile um. Nach dem Verschieben die Breiten per Layout-Preset im Zeilen-Header oder per Resize-Handle zwischen den Zellen korrigieren. (Gleiches Verhalten wie bei „Duplizieren" und „Einfügen".)
Die letzte verbleibende Spalte einer Zeile lässt sich nicht in eine andere Zeile ziehen — sonst bliebe eine Zeile ohne Spalten zurück. Der Editor zeigt in dem Fall einen Hinweis.
Zwischen zwei benachbarten Zellen liegt ein Resize-Handle: Ziehen verschiebt die Grenze und verteilt die Spans der beiden Zellen neu. Die Layout-Presets im Zeilen-Header (z.B. 4+8, 6+3+3) setzen gängige Aufteilungen mit einem Klick.
Zeilen- und Zellen-Panel sind in aufklappbare Sektionen gegliedert. Beim Öffnen ist nur „Layout & Abstände" aufgeklappt, alles Weitere liegt zugeklappt darunter:
| Sektion | Inhalt |
|---|---|
| Layout & Abstände | Container & Inhaltsbreite (nur Zeile), Innen-Abstände (oben / unten / Gap bzw. links-rechts), Außen-Abstände |
| Hintergrund | Farbe, Bild, Video |
| Ausrichtung & Verhalten | Textausrichtung, vertikale Ausrichtung, „Mobil umkehren" (nur Zeile) |
| Ecken & Effekte | Abgerundete Ecken, Schatten bei Hover, eigene CSS-Klasse |
| Animation | Art, Verzögerung, Dauer |
| Verlinkung | Ganze Zeile / Spalte als interner Link |
Mehrere Sektionen dürfen gleichzeitig offen sein. Ein Punkt in der Kopfzeile markiert Sektionen, in denen etwas vom Standard abweicht — so ist ohne Aufklappen erkennbar, wo an einer Zeile oder Spalte Werte hinterlegt sind.
Jede Abstandszeile besteht aus Regler + Zahlenfeld (0–16, Eingaben werden auf den gültigen Bereich begrenzt). Im Normalfall steuert dieser eine Regler alle drei Breakpoints gleichzeitig:
Abstand oben [────●────] [ 3 ] 🖥
Der Button rechts klappt die Zeile auf und macht jeden Breakpoint einzeln einstellbar:
Abstand oben 3 / 3 / 5 🖥
📱 Smartphone [────●────] [ 3 ]
💻 Tablet [────●────] [ 3 ]
🖥 Desktop [──────●──] [ 5 ]
Zwei Verhaltensregeln, die verhindern, dass Werte unbemerkt verlorengehen:
- Automatisches Aufklappen: Weichen die drei Werte in bestehenden Daten bereits voneinander ab, wird die Zeile immer aufgeklappt dargestellt. Ein einzelner Regler würde die abweichenden Tablet-/Desktop-Werte sonst beim ersten Anfassen überschreiben.
- Zuklappen vereinheitlicht: Klappt man bewusst zu, werden alle drei Breakpoints auf den Smartphone-Wert gesetzt. Das ist die einzige Stelle, an der Zuklappen die Daten verändert — und die inhaltliche Bedeutung von „ein Wert für alle Bildschirmgrößen".
Die abgerundeten Ecken funktionieren nach demselben Muster: ein Regler für alle vier Ecken, aufklappbar auf die Einzelecken. Zugeklappt werden alle Ecken auf den Wert von „oben links" vereinheitlicht.
Zeilen- und Zellen-Panel haben je einen Button „Einstellungen zurücksetzen" (Zeilen-Panel oben, Zellen-Panel in der Aktionsleiste des Untertabs Einstellungen). Die gerade geöffneten Sektionen bleiben dabei offen. Er setzt ausschließlich Gestaltungswerte auf die Standardwerte zurück: Hintergrund, Innen- und Außen-Abstände, Gap, Container/Breite, Ecken-Radien, Ausrichtung, Hover-Schatten, eigene CSS-Klasse, Animation und Verlinkung.
Unangetastet bleiben:
- Zeile — die Spaltenaufteilung und alle enthaltenen Zellen samt Inhalten
- Zelle — ihre Breite (
span) und alle enthaltenen Module
Vor dem Zurücksetzen wird eine Bestätigung abgefragt.
Zeilen und Zellen lassen sich kopieren (fa-copy) und an anderer Stelle wieder einfügen (fa-clipboard) — die Zwischenablage liegt im localStorage und funktioniert damit auch artikelübergreifend. „Duplizieren" (fa-clone) legt direkt eine Kopie neben dem Original an. Beim Einfügen/Duplizieren werden alle IDs neu vergeben.
| Einstellung | Werte | Ergebnis |
|---|---|---|
| Container | standard, full |
container mx-auto px-4 / w-full px-4 |
| Breite des Inhalts | standard, full |
erzwingt container / w-full |
| Abstand oben (innen) | 0–16 pro Breakpoint | pt-{n} md:pt-{n} lg:pt-{n} |
| Abstand unten (innen) | 0–16 pro Breakpoint | pb-{n} md:pb-{n} lg:pb-{n} |
| Spalten-Gap | 0–16 pro Breakpoint | gap-{n} md:gap-{n} lg:gap-{n} |
| Außen oben/unten/seitl. | 0–16 pro Breakpoint | mt-* / mb-* / mx-* (+ md:/lg:) auf der <section> |
| Hintergrundfarbe | TW-Klasse | direkt als Klasse |
| Hintergrundbild | Dateiname | background-image Style + Focuspoint |
| Hintergrundvideo | Dateiname | .video-docker |
| Textausrichtung | text-left, text-center, text-right |
direkt als Klasse |
| Mobil umkehren | bool | pb-mobile-reverse |
| Animation | animate.css-Klasse + Verzögerung + Dauer | x-intersect-class.once="animate__animated …" auf dem inneren Container (setzt animate.css + alpinejs-intersect-class voraus) |
| Verlinkung | interne Artikel-ID | rendert die Zeile als <a href="…"> (ganze Zeile klickbar) statt <div> |
| Einstellung | Werte | Ergebnis |
|---|---|---|
| Breite | 1–12 | col-span-{n} |
| Abstand oben | 0–16 pro Breakpoint | pt-{n} md:pt-{n} lg:pt-{n} |
| Abstand unten | 0–16 pro Breakpoint | pb-{n} md:pb-{n} lg:pb-{n} |
| Innen links/rechts | 0–16 pro Breakpoint | px-{n} md:px-{n} lg:px-{n} |
| Außen oben/unten/seitl. | 0–16 pro Breakpoint | mt-* / mb-* / mx-* (+ md:/lg:) auf dem Zell-Element |
| Ausrichtung vertikal | start, center, end |
— / flex flex-col justify-center / flex flex-col justify-end |
| Abgerundete Ecken | Stufe 0–9 je Ecke (0, xs, sm, md, lg, xl, 2xl, 3xl, 4xl, full) |
rounded-* bzw. rounded-tl-* / rounded-tr-* / rounded-bl-* / rounded-br-* |
| Hintergrundfarbe | TW-Klasse | direkt als Klasse |
| Hintergrundbild | Dateiname | background-image Style + Focuspoint |
| Hintergrundvideo | Dateiname | .video-docker |
| Animation | animate.css-Klasse + Verzögerung + Dauer | x-intersect-class.once="animate__animated …" (setzt animate.css + alpinejs-intersect-class voraus) |
| Verlinkung | interne Artikel-ID | rendert die Zelle als <a href="…"> (ganze Zelle klickbar). Wird ignoriert, wenn die übergeordnete Zeile bereits verlinkt ist |
Die Datei assets/tw-gridbuilder-grid.css enthält eine Tailwind-@source inline(...)-Safelist (damit Tailwind alle vom Pagebuilder dynamisch gebauten Utility-Klassen erzeugt) sowie die wenigen echten Custom-Klassen. Sie wird nicht automatisch im Frontend eingebunden.
Voraussetzung: ein Tailwind-v4-Build im Projekt. Nur dort werden die
@source-Direktiven ausgewertet. Ohne Tailwind-Build existieren die generierten Utility-Klassen (.grid,.gap-*,.pt-*,.bg-primary-500…) nicht — die Datei allein per<link>einzubinden genügt seit 2.2.0 nicht mehr.
Die CSS-Datei per @import in die Tailwind-Einstiegsdatei des Projekts einbinden — nach @import 'tailwindcss':
@import 'tailwindcss';
/* … Theme-@theme-Variablen (--color-primary-500 etc.) … */
@import '/path/to/redaxo/src/addons/tw_gridbuilder/assets/tw-gridbuilder-grid.css';Tailwind wertet die im Addon-CSS enthaltene @source inline(...)-Safelist mit aus und generiert die Utilities als echte @layer utilities. Die Hintergrundfarben (bg-primary-500, bg-secondary-500, bg-neutral-*) beziehen ihre Werte aus den --color-*-Theme-Variablen — eine andere Farbpalette im Theme wirkt automatisch, ohne Änderung am Addon.
Sollen weitere Farb- oder Spacing-Stufen im Backend wählbar sein, müssen deren Klassennamen sowohl in die @source inline(...)-Safelist (in tw-gridbuilder-grid.css) als auch in die entsprechenden Optionslisten in assets/tw-gridbuilder.js aufgenommen werden.
Liegt die Tailwind-Einstiegsdatei nicht am Addon-Pfad, kann das Addon tw-gridbuilder-grid.css bei jedem Seitenaufruf automatisch in ein konfigurierbares Zielverzeichnis kopieren (nur wenn die Quelldatei neuer ist), das dann per @import eingebunden wird.
Einmalig konfigurieren, z.B. in der REDAXO-Konsole oder einer install.php:
rex_addon::get('tw_gridbuilder')->setConfig(
'css_output_path',
rex_path::base('theme/private/css/tw-gridbuilder-grid.css')
);Leer lassen oder auf '' setzen um das Kopieren zu deaktivieren.
Eine Sicherheitsprüfung (Input-Validierung, XSS, CSRF, API-Zugriffe) wurde am 2026-06-30 durchgeführt. Ergebnis:
| Bereich | Status |
|---|---|
| SQL-Injection | ✅ Sicher — alle Queries parametrisiert oder (int)-Cast |
| CSRF | ✅ Sicher — rex::isBackend() && rex::getUser() als Gate |
| XSS (HTML-Output) | ✅ Sicher — htmlspecialchars(ENT_QUOTES) durchgängig |
eval() mit User-Werten |
✅ Sicher — Werte via var_export() escaped |
| API-Zugriffskontrolle | ✅ Sicher — Backend-Auth-Check vor jeder Operation |
CSS background-image URL |
Hinweis: Die Funktion pb_bg_image_style() bettet den Mediadateinamen ungequotet als CSS-url() ein. Ein Dateiname mit Sonderzeichen wie ) oder ; könnte die CSS-Deklaration brechen. Da nur Backend-Admins Dateien hochladen können, ist das Risiko gering. Empfehlung für künftige Versionen: url("...") mit gequotetem, geslashtem Dateinamen verwenden.
- Teilweise theme-abhängig: Projektspezifische Tailwind-Klassen (z.B.
bg-primary-500,bg-secondary-50) kommen aus dem kompilierten Tailwind-Build des Themes. Alle strukturellen Klassen (Grid, Abstände, Container, video-docker) sind vollständig intw-gridbuilder-grid.cssenthalten. - Spalten-Spans werden beim Verschieben/Duplizieren/Einfügen nicht normalisiert — eine Zeile kann dadurch mehr oder weniger als 12 Spalten belegen und im Frontend umbrechen. Bewusste Entscheidung: eine automatische Korrektur würde manuell eingestellte Breiten überschreiben. Korrektur per Layout-Preset oder Resize-Handle.
- Modul-Input muss MForm-kompatibel sein
- CKEditor 5 (
cke5) und CKEditor 4 (ckeditor) werden unterstützt;redactorund andere Rich-Text-Editoren nicht getestet
MIT License, siehe LICENSE.md.