Custom-CSS-Konvention
Jede Row, Column und Widget-Instanz im Pagebuilder kann eigenes CSS tragen — der Redakteur tippt es im Properties-Panel (CodeMirror-Editor) ein. Der Core wrapped das CSS in eine scope-eindeutige Klasse, sammelt alles aus der ganzen Seite ein und baut daraus die fertige CSS-Bundle-Datei. Diese Seite erklaert die Wrapper-Klassen, CSS-Nesting, Cache-Integration und PurgeCSS-Safelist.
Wrapper-Klassen
Der Core vergibt automatisch eindeutige Klassen auf drei Ebenen:
| Element | Klasse | Beispiel |
|---|---|---|
| Row (Section) | .row_{id} | .row_42 |
| Column | .col_{id} | .col_108 |
| Widget (Instanz) | .widget_{base_id} | .widget_217 |
{id} ist die Primary-Key-ID aus der entsprechenden Tabelle:
row_42→page_row.id = 42col_108→page_row_columns.id = 108widget_217→page_row_col_widget.base_id = 217
widget_{base_id}, nicht widget_{id}
Bei Widgets wird der Wrapper mit der Base-ID gebildet — nicht mit der Widget-Row-ID. Multi-Language-Varianten derselben Widget-Instanz teilen sich dieselbe Base-ID und damit dieselbe Wrapper-Klasse. So muss man CSS nur einmal pflegen.
Custom-CSS eingeben
Im Pagebuilder-Properties-Panel gibt es fuer jedes Element einen CodeMirror-Editor mit LESS-Syntax-Validierung. Beispiel-Input fuer ein Widget:
color: #4F46E5;
padding: 2rem;
border-radius: 0.75rem;
background: white;Der Redakteur schreibt nur den Inhalt des Wrappers — keine Selektoren. Der Core wrapped das vor dem Cache-Build:
.widget_217 {
color: #4F46E5;
padding: 2rem;
border-radius: 0.75rem;
background: white;
}CSS-Nesting fuer Kind-Selektoren
Fuer Styling von Unter-Elementen kann der Redakteur CSS-Nesting nutzen — das CSS wird als LESS geparst, Nesting ist unterstuetzt:
color: #111;
h3 {
color: #4F46E5;
font-weight: 600;
}
h3 i {
margin-right: 0.5rem;
}
&:hover {
opacity: 0.9;
}Compiled:
.widget_217 { color: #111; }
.widget_217 h3 { color: #4F46E5; font-weight: 600; }
.widget_217 h3 i { margin-right: 0.5rem; }
.widget_217:hover { opacity: 0.9; }& referenziert den Wrapper-Selektor — wie in Sass/LESS ueblich. Auch Media-Queries und Pseudo-Klassen funktionieren nested.
Cache-Integration
cache_compress_css.php — der Builder
Das Script _public/extensions/core/backend/cache_settings/script/cache_compress_css.php iteriert ueber alle Rows/Columns/Widgets der Seite und wrapped das jeweilige custom_css:
// Auszug aus cache_compress_css.php
// Rows — wrap with .row_{id} { ... }
foreach ($rows as $row) {
$css .= parseCustomLess(
".row_" . $row['id'] . " { " . $row['custom_css'] . " }",
'row_' . $row['id']
);
}
// Columns — wrap with .col_{id} { ... }
foreach ($cols as $col) {
$css .= parseCustomLess(
".col_" . $col['id'] . " { " . $col['custom_css'] . " }",
'col_' . $col['id']
);
}
// Widgets — wrap with .widget_{base_id} { ... }
foreach ($widgets as $w) {
$css .= parseCustomLess(
".widget_" . $w['base_id'] . " { " . $w['custom_css'] . " }",
'widget_' . $w['base_id']
);
}parseCustomLess() nutzt den LESS-Parser — Nesting, Variablen und Mixins sind moeglich. Das komplette Output landet in der globalen CSS-Bundle-Datei.
Wann wird der Cache neu gebaut?
- Nach Aenderungen am Custom-CSS (via Pagebuilder-Save)
- Nach Aenderungen an Design-Tokens (
/admin/design→ LESS-Editor) - Manuell via
/admin/cache→ "Cache leeren"
Der Cache-Rebuild ist global — alle Seiten werden neu kompiliert. Das ist einmalig aufwendig, danach laeuft das CSS ohne Runtime-Overhead aus.
Spacing- und Flex-CSS
Zusaetzlich zu Custom-CSS generiert der Core automatisch Media-Query-basierte Regeln fuer Spacing (Padding/Margin) und Flex-Alignment:
// Aus cache_compress_css.php
// Spacing → margin-top/right/bottom/left + padding-*
$css .= generateSpacingCssBlock('.row_' . $row['id'], $row['spacing']);
// Flex → align-items, justify-content
$css .= generateFlexCssBlock('.row_' . $row['id'], $row['flex_settings']);Beide nutzen die Responsive-Breakpoints (1200px / 992px / 768px) aus dem Design-System. Redakteure pflegen Spacing/Flex ueber ein Webflow-aehnliches Box-Modell in den Properties-Panel-Tabs — nicht als custom_css-Eingabe.
Spacing nie inline, nie per Custom-CSS
Wer margin-top: 2rem in den Custom-CSS-Editor tippt, sabotiert das Responsive-System. Fuer Spacing immer den Spacing-Tab im Properties-Panel verwenden — dort gibt es pro Breakpoint separate Werte. Details: CLAUDE.md-Sektion "Pagebuilder — Spacing & Flex".
PurgeCSS-Safelist
Weil .row_{id}, .col_{id}, .widget_{id} dynamisch generiert sind, kann PurgeCSS sie beim Build nicht automatisch erkennen. Die nuxt.config.ts des Themes hat sie in der Default-Safelist — wichtig: im greedy-Bucket, nicht standard:
// _theme/vue-base/nuxt.config.ts (Ausschnitt)
safelist: {
standard: [
/^swiper/, /^vjs/, /^fa-/, /^ns-/,
/^col-/, /^row/, /^active/, /^show/, /^modal/, /^elw-/
],
greedy: [
/^cc/, /^cm/, // Cookie-Consent
/^row_/, /^col_/, /^widget_/, /^sw6/ // Pagebuilder-Wrappers
],
deep: [
/data-animation/, /ns-anim-safety/ // GSAP-Animationen
]
}greedy ist hier entscheidend: Pagebuilder-Klassen treten oft in Kombinationen wie widget_217 is-active oder row_42 col-6 auf — standard matcht nur die genauen Klassennamen, nicht zusammengesetzte Selektoren. greedy matcht den Prefix innerhalb des class-Attributs und zieht den gesamten Selector-Kontext ins Bundle.
Eigene Custom-CSS-Klassen-Namen
Wer im Custom-CSS eine eigene Klasse nutzt (z. B. .my-custom-button), muss sie zusaetzlich in die Projekt-Safelist eintragen — sonst entfernt PurgeCSS sie. Via ENV ohne nuxt.config.ts-Patch:
NUXT_PURGECSS_SAFELIST_STANDARD=my-custom-,xyz-Details: Konfiguration › PurgeCSS-Safelist.
AI-generiertes CSS
Das AI-Layout-Feature schreibt CSS in dieselbe custom_css-Spalte — nicht als inline styles. Dadurch ist AI-Output nachtraeglich per CodeMirror-Editor bearbeitbar. Konvention: AI nutzt ebenfalls CSS-Nesting mit Child-Selektoren wie h3 i { color: #4F46E5 }.
Datenbank-Spalten
Jede der drei Element-Tabellen hat eine custom_css-Spalte:
| Tabelle | Spalte | Inhalt |
|---|---|---|
page_row | custom_css | LESS/CSS fuer .row_{id} |
page_row_columns | custom_css | LESS/CSS fuer .col_{id} |
page_row_col_widget | custom_css | LESS/CSS fuer .widget_{base_id} |
Das Auto-Format-Feature (formatCss()) prettifiziert minifiziertes CSS beim Laden in den CodeMirror-Editor — der Redakteur sieht immer formatierten Code, auch wenn die DB den minifizierten Output speichert.
Haeufige Fehler
Selektor im Custom-CSS-Editor geschrieben
Der Editor erwartet nur den Wrapper-Inhalt, nicht den Selektor. .widget_217 { color: red; } fuehrt nach dem LESS-Compile zu .widget_217 .widget_217 { color: red; } — ein Descendent-Selector, der nichts matcht, weil es kein verschachteltes .widget_217 im DOM gibt. Der Redakteur sieht den Fehler nicht direkt, aber das CSS wirkt nie. Einfach nur color: red; eintippen.
.widget_217 hardcoded in Theme-CSS
Theme-CSS sollte niemals konkrete .widget_217-Klassen referenzieren — die ID aendert sich bei Seitenkopien/Varianten. Fuer Theme-uebergreifende Widget-Styles im Widget-Template selbst (template/index.vue → <style scoped>).
Inline-Styles via v-bind im Template
Dynamische Styles per :style="{ color: widget.data.color }" sind pragmatisch fuer User-einstellbare Farben — aber umgehen das Custom-CSS-System. Fuer strukturelles Styling lieber Custom-CSS mit CSS-Variablen kombinieren: Widget setzt style="--my-color: ...", Custom-CSS nutzt color: var(--my-color).
PurgeCSS entfernt eigene Utility-Klassen
Wer im Custom-CSS auf eine Klasse verweist, die im Theme-CSS definiert ist (z. B. .text-primary), kann es passieren, dass PurgeCSS die Klasse aus dem Theme-Bundle rauswirft — weil sie nirgendwo im Template-HTML vorkommt. Klasse in die NUXT_PURGECSS_SAFELIST_STANDARD aufnehmen.
Spacing via Custom-CSS
margin/padding im Custom-CSS-Editor bricht das Responsive-System. Fuer Abstaende immer den Spacing-Tab im Properties-Panel nutzen — der erzeugt Media-Query-basiertes CSS pro Breakpoint.
Siehe auch
- Widget-Anatomie — Widget-Instanz-ID und
base_id - widget_menu-Feldtypen — wo das Properties-Panel-Formular herkommt
- Konfiguration › PurgeCSS-Safelist — projektspezifische Safelist-Erweiterung
- Plugins › API-Endpunkte —
backend/pagebuilder-Actions (save_row/column/widget CSS) _public/extensions/core/backend/cache_settings/script/cache_compress_css.php— Builder-Code_public/src/css/frontend/custom.less— globale Design-Tokens