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 (hero → pb-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 chunk pagebuilder_hero:

fenom
{set $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">
    <h2 class="pb-hero__title">{$title|pb_text}</h2>
    {if $description}
      <div class="pb-hero__description">{$description|pb_text}</div>
    {/if}
    {if $button_label && $button_url}
      <a class="pb-hero__button pb-button" href="{$button_url|pb_href|escape}">{$button_label|pb_text}</a>
    {/if}
  </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 typeMODXFenom
text, textarea[[+title]]{$title|pb_text}
url in href[[+button_url]]{$button_url|pb_href|escape}
image[[$pagebuilder_partial_image]]{include 'pagebuilder_partial_image' image=$image alt=$alt}
richtext, editorjs[[+content]]{$content}

pb_href normalizes MODX links. pb_text is a Fenom modifier only. Output rich text only if you trust editors who can save the resource. Print text and textarea with pb_text, not escape.

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.