Skip to content

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:

ElementKlasseBeispiel
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_42page_row.id = 42
  • col_108page_row_columns.id = 108
  • widget_217page_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:

css
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:

css
.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:

css
color: #111;

h3 {
    color: #4F46E5;
    font-weight: 600;
}

h3 i {
    margin-right: 0.5rem;
}

&:hover {
    opacity: 0.9;
}

Compiled:

css
.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:

php
// 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:

php
// 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:

ts
// _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:

env
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:

TabelleSpalteInhalt
page_rowcustom_cssLESS/CSS fuer .row_{id}
page_row_columnscustom_cssLESS/CSS fuer .col_{id}
page_row_col_widgetcustom_cssLESS/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