Editorial cover graphic with a concentric ring motif in gold on cream.

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.

bootstrap_layout_builder.layouts.yml (excerpt)
two_column_6_6:
label: 'Two column (6/6)'
template: 'layout--bs-two-column'
regions:
first:
label: 'First column'
second:
label: 'Second column'
layout--bs-two-column.html.twig
<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:

bootstrap_layout_builder admin overrides
.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