
Why a block looked full-width in Layout Builder, half everywhere else
How Bootstrap Layout Builder maps grid columns onto regions, and why the admin preview and the themed page are rendering two different things.
The friction
An editor filed it as a bug: a two-column section they’d built showed both blocks side by side in the front-end preview, and stacked full-width, one on top of the other, in the Layout Builder editing screen. Same page, same layout, same blocks. It looked like the grid was broken. It wasn’t. Layout Builder and the themed page were rendering two genuinely different templates, and Bootstrap’s grid classes only existed in one of them.
Two render paths, one layout
A Layout Builder layout plugin’s build() method returns a render array keyed by region
ID. What happens to that render array from there depends on which theme is asked to
render it, and there are two candidates in play at any moment: the front-end theme,
which owns the page an anonymous visitor sees, and Layout Builder’s own administration
UI, which wraps every region in its own drag-and-drop scaffolding so an editor can
reorder blocks by hand.
Bootstrap Layout Builder’s job is to take a layout plugin’s regions and wrap them in the
markup Bootstrap’s grid actually needs: a .row around the layout, .col-md-6 (or
whatever the configured breakpoint split is) around each region.
two_column_6_6: label: 'Two column (6/6)' template: 'layout--bs-two-column' regions: first: label: 'First column' second: label: 'Second column'<div class="row"> <div class="col-md-6">{{ content.first }}</div> <div class="col-md-6">{{ content.second }}</div></div>That template is what the front-end theme renders. It’s also what the editing screen is
supposed to render. Layout Builder is meant to reuse the site’s own layout templates so
what you see while editing matches what ships, but the drag-and-drop overlay injects its
own wrapper markup around each region for the reordering controls, and depending on how a
theme’s breakpoint CSS is scoped, that extra wrapper can sit between the .row and the
.col-md-6 elements in a way that breaks Bootstrap’s direct-child selector expectations.
Bootstrap’s grid CSS targets .row > .col-* deliberately, so that nesting a grid inside a
column doesn’t accidentally apply the outer row’s rules to the inner one. Layout Builder’s
own admin wrapper, inserted between them, is exactly the kind of intervening element that
selector is designed to exclude.
Why it reads as “broken” rather than “different”
The frustrating part of debugging this the first time wasn’t the CSS. Once you know to look at the DOM structure in the editing screen versus the live page, the extra wrapper is obvious in the inspector. It was that everything else about the editing screen goes to real lengths to look like the live page: same fonts, same spacing, same component styles. That fidelity is what makes the one place it diverges so disorienting. An editor has no reason to suspect the grid math is running against different HTML than production is, because nothing else about the screen suggests two different render paths exist at all.
What the module does about it
Bootstrap Layout Builder can’t remove Layout Builder core’s admin wrapper. That scaffolding is how block reordering works, and plenty of other contrib code depends on its markup staying put. What it does instead is apply the same breakpoint classes to an element one level higher, so the direct-child selector still resolves correctly with the wrapper in the middle:
.layout-builder .row > .layout-builder__region { display: contents;}display: contents removes the wrapper from the box model without removing it from the
DOM. Layout Builder’s drag handles and region metadata stay exactly where core expects
them, but Bootstrap’s .row > .col-* selector now sees the columns as direct children
again, because as far as layout is concerned, they are.
What is still not fully settled
display: contents has known accessibility caveats. It can strip an element out of the
accessibility tree along with its box, which matters if a screen reader relies on that
wrapper’s role or label for anything beyond visual layout. On the elements Layout Builder
uses this on, testing so far hasn’t turned up a regression, but “hasn’t turned up a
regression yet” isn’t the same claim as “verified safe across every screen reader and
region-wrapper combination a theme might produce.” I’d rather find out from someone
running an existing site through this than assume it from a handful of manual passes.
What this came from
- ModuleBootstrap Layout Builder