Skip to content
  1. Extras
  2. PageBuilder
  3. Frontend output
  4. Design system

Design system (front)

Section styles on the site are separate from PrimeVue in the manager. Chunks render markup with the pb- prefix, and pagebuilder-sections.css sets layout, typography, and tokens inside .pb-page.

Loading CSS and JS

The PageBuilder snippet calls pbRegisterFrontendAssets() when pagebuilder_load_frontend_css = 1 (or &load_css=1``). Files are registered via regClientCSS with a ?v= asset version query.

Parameter / settingDefaultEffect
pagebuilder_load_frontend_css1Enables snippet CSS globally
load_cssfrom settingOverrides loading on a single call
wrap_pagesame as load_cssWraps HTML in <div class="pb-page">

Disable styles on one page: [[!PageBuilder? &load_css=0]]. Keep the wrapper with &wrap_page=1`` and load_css=0 if you set tokens yourself.

pagebuilder-sections.js loads with CSS when the file is shipped with the extra. It initializes carousels (data-pb-carousel) and tabs (data-pb-tabs) inside .pb-page.

See Frontend output and PageBuilder → Parameters.

Stylesheets

Path from the site root: assets/components/pagebuilder/css/.

FileWhen it loads
pagebuilder-sections.cssAlways when load_css=1
pagebuilder-sections-pro.cssWith capability pro
pagebuilder-commerce.cssWith capability pro (product-card, spotlight, promo)

Pro and commerce CSS are not loaded on a Free build, even if the section chunk lives in the theme.

.pb-page wrapper

Tokens are set on .pb-page, not :root. The global site theme stays untouched; sections get their own vertical rhythm.

Direct siblings inside .pb-page are spaced with:

css
.pb-page > * + * {
  margin-top: var(--pb-section-gap);
}

The inner container is .pb-section__inner with max-width: var(--pb-content-max) and horizontal padding var(--pb-space-inline).

CSS tokens

Defaults from pagebuilder-sections.css. Override them in your theme on the same .pb-page selector.

TokenDefaultPurpose
--pb-section-gap4remGap between sections
--pb-content-max72remInner max-width
--pb-space-sm1remSpacer --sm
--pb-space-md2remSpacer --md
--pb-space-lg4remSpacer --lg
--pb-space-xl6remSpacer --xl
--pb-space-inlinevar(--pb-space-sm).pb-section__inner padding
--pb-radius0.5remBorder radius
--pb-color-textinheritBody text
--pb-color-mutedcolor-mix(...)Secondary text
--pb-color-accent#2563ebAccent, prose links
--pb-color-surface#fffCard background
--pb-color-ink#0f172aDark text on light
--pb-color-bordercolor-mix(...)Borders
--pb-color-on-accent#fffText on accent
--pb-color-danger#dc2626Form errors
--pb-color-danger-bg#fef2f2Error background
--pb-color-danger-text#991b1bError text
--pb-color-success-bg#ecfdf5Success background
--pb-color-success-text#065f46Success text
--pb-shadow-cardtwo-layer shadowCards, .pb-surface
--pb-button-bgvar(--pb-color-accent)CTA background
--pb-button-colorvar(--pb-color-on-accent)CTA text
--pb-grid-gap1.5remcards, gallery, stats grids
--pb-gallery-columns3Gallery columns on wide screens
--pb-stats-columns4Stats columns
--pb-hero-overlayrgb(0 0 0 / 45%)Hero overlay
--pb-avatar-size3remTestimonial avatars
--pb-prose-linkvar(--pb-color-accent)Richtext links
--pb-video-ratio16 / 9 (Pro CSS)Video embed

--pb-hero-bg is set inline on the section (background URL), not on .pb-page.

Theme example

css
.pb-page {
  --pb-color-accent: #059669;
  --pb-content-max: 60rem;
  --pb-section-gap: 2.5rem;
  --pb-space-inline: 1.25rem;
}

BEM

Block prefix: pb-. Block name matches the section key (heropb-hero).

LevelPatternExample
Blockpb-{key}pb-hero, pb-faq
Section shellpb-section, pb-section--{key}pb-section--cta
Elementpb-{block}__*pb-hero__title, pb-section__inner
Modifierpb-{block}--*pb-hero--center, pb-spacer--md

On the section root:

  • class="pb-section pb-section--hero pb-hero …"
  • data-pb-section="hero" for debugging and styles
  • id="pb-{id}" when the document section has an id

Shared primitives from base CSS:

ClassPurpose
pb-button, pb-button--smCTA and link buttons
pb-headingSection title
pb-grid, pb-grid--cardsCSS Grid
pb-surfaceCard with shadow
pb-spacer, pb-spacer--mdVertical gap inside a section
pb-listing, pb-listing__gridCatalog wrappers (Pro commerce)
pb-carousel, pb-tabsInteractive (Pro + JS)

Spacer: two classes pb-spacer pb-spacer--md, not pb-spacer-md.

Hero button: pb-hero__button pb-button. Hero background: CSS var --pb-hero-bg, not a bare inline background-image without the variable.

Fenom section shell

Stock hero chunk (simplified):

fenom
{var $heroBg = is_array($background) ? ($background.url ?: '') : ($background ?: '')}
<section class="pb-section pb-section--hero pb-hero{if $alignment == 'center'} pb-hero--center{/if}{if $cssClass} {$cssClass|escape}{/if}"
  data-pb-section="hero"{if $id} id="pb-{$id|escape}"{/if}{if $heroBg} style="--pb-hero-bg: url('{$heroBg|escape}')"{/if}>
  <div class="pb-section__inner pb-hero__inner">
    <h1 class="pb-hero__title">{$title|escape}</h1>
    ...
  </div>
</section>

Chunk variables:

  • $cssClass from data.cssClass (event pbOnBeforeRenderSection)
  • $id is the section id in the document JSON
  • section fields by name from JSON ($title, $background, …)

Build custom sections the same way. Checklist: Developer → Section definition.

Partial pagebuilder_partial_image

Shared chunk for <img> in gallery, testimonials, image, and Pro sections:

fenom
{include 'pagebuilder_partial_image' image=$item.image alt=$item.alt class='pb-gallery__media'}
ParameterDescription
imageURL string or image field array (url)
altAlt text
classCSS class on <img>
loadingDefault lazy

The partial renders nothing when the URL is empty.

Field typeFenom
text, textarea|escape
url in href|pb_href|escape (MODX link normalization)
richtext, editorjsEditor HTML with no front-end sanitize

Output rich text only if you trust editors who can save the resource. Escape everything else.

Extra class via event

In pbOnBeforeRenderSection you can append data.cssClass before the chunk runs:

php
case 'pbOnBeforeRenderSection':
    $pipeline = $scriptProperties['pipeline'] ?? null;
    if ($pipeline instanceof \PageBuilder\Section\SectionRenderPipeline) {
        $sections = $pipeline->sections();
        if (isset($sections[0])) {
            $sections[0]['data']['cssClass'] = trim(($sections[0]['data']['cssClass'] ?? '') . ' is-promo');
            $pipeline->replaceSection(0, $sections[0]);
        }
    }
    break;

The event runs only on HTML cache miss (use_cache=0 for debugging). See Manager and events.

Front-end interactivity

Dependency-free pagebuilder-sections.js:

MarkerBehavior
[data-pb-carousel]Scroll .pb-carousel__track, dots, autoplay with data-pb-autoplay="1"
[data-pb-tabs]Panel switch, URL hash via data-pb-anchor

Respects prefers-reduced-motion: reduce (autoplay and smooth scroll off).

Commerce styles (Pro)

pagebuilder-commerce.css styles product cards and storefront blocks: .pb-product-card, .pb-product-spotlight, promo banners. Listing sections often add pb-listing next to the block (pb-products-grid pb-listing).

Same tokens as .pb-page. Recolor commerce via --pb-color-accent, --pb-color-surface, --pb-shadow-card.

Anchors and sticky header

Sections with id="pb-…" and listing product cards use scroll-margin-top: 5.5rem so a sticky site header does not cover the anchor target.

Spacer migration

Class pb-spacer-md was replaced with pb-spacer--md. After upgrade, check custom theme CSS and your own chunks.