Show codeHide code
src/layout - 8 files
Six primitives and two helpers. None of them uses a breakpoint class. Where a layout has to change shape it does so from its own width, so the same markup is correct on a phone, inside a grid cell and inside a drawer.
Every example below is live, and every one sits in a container you can resize with the slider under it. That control is the whole argument of this page: these primitives reflow from their own width, so shrinking the box they are in has to change their shape without the viewport moving at all. Drag one and watch. If the claim were false the box would just clip.
The markup panels below are read out of the live examples by site.js, and the width controls come from src/behaviour/demo-stage.js. With scripting off the examples still render at their natural width, the sliders do nothing and the panels stay empty.
Centres a column, bounds its width and owns the gutter between content and the
viewport edge. The block is bs-wrap rather than
bs-container, because --bs-container-* is already the
global width scale and a block named container would sit in the namespace of the scale
it reads from.
Put the class on the element that already means something rather than wrapping a
landmark in a spare div. The gutter is declared twice, the second time through
env(), so content never sits under a notch.
Widths: --xs --sm --md --lg --xl --2xl from the container
scale, --prose for a reading measure in ch,
--full for a section that owns its own background, and
--flush to drop the gutter. A shell inside a shell pays
the gutter once.
One consistent gap between every child, owned by the parent. A margin belongs to the child, so spacing between two blocks depends on which two they are and on whose margin wins the collapse. A gap belongs to the container, so the rhythm survives reordering, filtering and conditional rendering.
Modifiers: --tight --loose --flush for the gap, and
--start --center --end --between for alignment. Use
--bare on a ul, and add
role="list": removing the marker removes the list
semantics in Safari and VoiceOver, so the item count is lost without it.
A horizontal group that wraps. Tag lists, button rows, meta lines, toolbars: anything where the item count is unknown and the container width is not. Wrapping is not an option here, it is the default, because a row that cannot wrap either overflows the page or needs a horizontal scroller, and at 320px with text scaled to 200% it will do one of the two.
Keep the gap at or above --bs-space-2 for a row of
interactive targets so they stay separable under WCAG 2.2 SC 2.5.8. Alignment
modifiers: --start --center --end --between --baseline --stretch.
No breakpoint classes. The column count is a function of the grid's own width, so the same markup is correct full bleed, inside a sidebar and inside another grid cell. A hand-written column system cannot do that, because it is keyed to the viewport and the viewport does not know how much room the component was given.
--bs-grid-min sets the track floor.
--fill keeps empty tracks instead of collapsing them,
--rows uses subgrid so headings and footers line up across
a row, --query makes each cell a container query context,
and bs-grid__full spans a cell across the row. Place items
in DOM order: moving them visually with grid placement breaks reading and focus order.
Two panes at an authored ratio, stacked when they no longer both fit. The switch comes from the layout's own width, not from a breakpoint, so a split nested in a grid cell or a drawer collapses at the right moment rather than at the viewport's.
Ratios: --2-1 --3-1 --1-2 --1-3, with no modifier meaning
even. --bs-split-min is the width below which the panes
stop sharing a line.
A fixed-ish side column beside a fluid main column, collapsing to one column when there is no longer room for both. No query is involved at all: the main column declares the smallest share of the line it will accept, and flex wraps the moment that share cannot be met. Nothing has to be told the breakpoint, so nothing can be told the wrong one after the component is moved. This page uses it.
To put the rail on the right, swap the two elements in the DOM. Never use
order or row-reverse: they
move the visual order without moving focus and reading order, which fails SC 1.3.2 and
SC 2.4.3.
Centres a column horizontally and bounds it to a readable measure. The default maximum is a measure rather than a layout width, because this primitive exists for content that is read and line length is a function of the font, not of the viewport.
Centred on its own intrinsic width.
--intrinsic centres children on their own width,
--full and --viewport centre
on both axes. Centring is a visual arrangement only and never changes DOM order.
An empty flex item that absorbs the free space in a stack, cluster or sidebar, pushing what follows to the far end. Reach for it when the split point is data driven, so space-between is wrong the moment an item is absent or a third appears.
The element stays empty. It carries no text, no icon and no role, so it contributes nothing to the accessibility tree and needs no aria-hidden. Only inside a flex parent: in a grid an empty cell consumes a track rather than the free space.