Theming blocks
The component ships a markup layer for block types: content width, the vertical rhythm between sections, and a set of tokens a block measures its spacing and colors by. It does not touch fonts, buttons or typography — those come from your theme, and overriding them would mean fighting it on every page.
Two files, in this order:
| File | What it is |
|---|---|
pb.blocks.css | The contract: tokens, content width, section rhythm. Every block type stands on it. |
pb.starter.css | The markup of the shipped block types, plus pb-tone--* and pb-grid--*, which any block may use. |
The order matters — pb.blocks.css declares the tokens pb.starter.css stands on. That is why styleUrls() returns a list rather than a single URL.
They are two files rather than one so a theme can replace the shipped blocks' markup and keep the rhythm: drop pb.starter.css, keep pb.blocks.css, and your own blocks still line up with everything else.
You do not normally link these yourself
On an ordinary MODX page the layer is registered for you — see System settings. The {pb_styles()} tag is only for a template the component's own router renders.
The markup contract
<div class="pb-page">
<section class="pb-section pb-cards">
<div class="pb-section__inner">...</div>
</section>
</div>Three classes do the work:
.pb-page— the wrapper. The only place the tokens are declared..pb-section— vertical rhythm. Takes half of--pb-section-gapas padding above and below, rather than a margin between neighbours, so a section with a background has inner padding of its own and two neighbouring backgrounds meet flush..pb-section__inner— the content column:max-width: var(--pb-content-max), centered, with--pb-gutterat the sides. Put the background on the section and it bleeds to the window edge while the content stays in the column.
Then pb-{key} for the block's own key, and BEM below it — .pb-cards__grid, .pb-hero__media, .pb-faq__question.
The first and last child of .pb-section__inner have their outer margins zeroed, or a heading carrying your theme's margin would eat the section's padding and the rhythm would drift. Both rules use :where(), so they carry zero specificity and any ordinary selector in your block's styles beats them.
Section modifiers
| Class | Effect |
|---|---|
.pb-section--narrow | Column drops to --pb-content-narrow |
.pb-section--wide | Column grows to --pb-content-wide |
.pb-section--bleed | No column and no side padding — for a map, a slider, a full-width image |
.pb-section--flush | No vertical padding, so neighbouring blocks meet |
Tokens
Declared on .pb-page in pb.blocks.css:
| Token | Default |
|---|---|
--pb-content-max | 1200px |
--pb-content-narrow | 48rem |
--pb-content-wide | 1600px |
--pb-section-gap | 5rem (3rem below 48rem) |
--pb-gutter | 1.25rem |
--pb-gap | 1.5rem (1rem below 48rem) |
--pb-radius | 0.5rem |
--pb-accent | currentColor |
--pb-muted | 65% of currentColor |
--pb-surface | 5% of currentColor |
--pb-border | 1px solid, 18% of currentColor |
pb.starter.css adds one more: --pb-surface-strong, 12% of currentColor.
Colors are derived from currentColor on purpose — a block inherits your theme's text color instead of bringing its own palette, so the same markup works on a light theme and a dark one without inverting anything.
Overriding
Set the token again on the same selector, from your own stylesheet:
.pb-page {
--pb-content-max: 1100px;
--pb-section-gap: 4rem;
--pb-surface: #f5f5f4;
}Why .pb-page and not :root
On :root your theme and the component write to the same place, and which one wins starts to depend on the order the files are linked in. On the wrapper you override deliberately, with the same selector.
And it wins regardless of link order: the whole layer sits in @layer pageblocks, and an unlayered rule beats any layer. So you never have to edit the component's files to retheme — an upgrade overwrites those and would take your changes with it.
The same applies to a single rule, not just tokens: an ordinary unlayered selector in your theme beats the layer without !important and without raising specificity.
Getting your own class onto the wrapper
Only needed if your theme hooks the container with its own selectors. The wrapper is the chunk pageblocks.blocks.wrapper, which pbBlocks uses when &tplWrapper is not given. Copy it under your own name, add the class, and pass it:
{pbBlocks tplWrapper='my.page.wrapper'}Do not edit the shipped chunk to do this — overriding a token does not need it, and the upgrade check treats an edited default as yours to keep.
Lose the wrapper, lose the tokens
Pass &tplWrapper=`` only when your site template already provides the page container and you have declared the tokens yourself. Without .pb-page there are no tokens on the page at all, and the catalog's sections spread across the full window width.
The same trap applies to rendering a block straight from Fenom with {$block->render()}: that output is not inside the wrapper unless you put it there.
Utilities any block can use
From pb.starter.css, so they are available to your own block types too:
| Class | Effect |
|---|---|
.pb-tone--surface | Background of --pb-surface |
.pb-tone--accent | Background of --pb-surface-strong |
.pb-grid--2 / --3 / --4 | A grid of exactly 2, 3 or 4 equal columns, --pb-gap apart |
The grids use a fixed column count rather than auto-fit: the editor picks "three columns" and expects three, not as many as happened to fit. Columns are minmax(0, 1fr), not 1fr, so one long word or a wide image cannot stretch its column and break the grid.
All three collapse to one column below 48rem. Between 48.0625rem and 64rem, .pb-grid--4 drops to two — four columns on a tablet are unreadable, while three still hold.
Turning the layer off
pageblocks_load_styles is the single switch, and it is also the answer to "I will handle block styles myself". Off, styleUrls() returns an empty list and {pb_styles()} an empty string.
Off means the tokens are gone too
The shipped block types are built against these tokens. Switch the layer off and your theme has to declare --pb-content-max, --pb-section-gap, --pb-gap and the rest on .pb-page itself, or the shipped blocks have nothing to measure by. To change a few values, override them — that needs no switch.