The four primitives
CSS layout in 2025 is dominated by four mechanisms. Each has a preferred use case; mixing them inappropriately produces brittle layouts.
| Primitive | Mental model | Best for |
|---|---|---|
| Flow | Block-level boxes stack; inline content runs along the writing axis. | Prose, default vertical rhythm. |
| Flex | A one-dimensional axis where children distribute, align, and grow. | Single-axis distribution: nav bars, button groups. |
| Grid | A two-dimensional track system with named lines and areas. | Page-level layouts, complex two-axis arrangement. |
| Container queries | Sizing decisions based on the containing block, not the viewport. | Reusable components placed in containers of varying width. |
Flow: the default
Every block-level element is in flow unless declared otherwise.
Margins collapse vertically, line boxes wrap, and the writing direction
follows writing-mode. Authors who avoid position: absolute and
respect flow for prose-heavy content gain accessible reading order
and predictable reflow on zoom.
Flex: distribution along one axis
Flexbox solves the “distribute these items along a line” problem.
Authors set display: flex on the parent and pick a flex-direction
(row or column). The cross axis is implicit. The most common
patterns:
.row { display: flex; gap: var(--space-3); align-items: center; }
.row .grow { flex: 1; }
gap replaces the old margin: 0 1em 0 0; &:last-child { margin: 0; }
pattern. flex: 1 is shorthand for 1 1 0, meaning the item grows,
shrinks, and starts from a zero basis.
Flex’s gotcha: items have a min-width: auto default, which means
text content can prevent shrinking below the intrinsic minimum. Set
min-width: 0 on flex items that wrap text or use min-content.
Grid: two-dimensional layout
display: grid defines a grid container. Rows and columns are
declared with grid-template-rows, grid-template-columns, or the
shorthand grid-template-areas. The two-axis nature lets a single
declaration solve layouts that would require nested flex containers.
.page {
display: grid;
grid-template-columns: minmax(0, 1fr) min(72ch, 100%) minmax(0, 1fr);
gap: var(--space-4);
}
.page > * { grid-column: 2; }
.page > .full { grid-column: 1 / -1; }
The 1fr unit divides the remaining track space proportionally.
minmax(0, 1fr) is the safe variant when content might overflow.
The subgrid keyword (grid-template-columns: subgrid) lets a
nested element inherit its parent’s tracks; this reached cross-engine
support across Chromium (Blink), WebKit, and Gecko in 2024.
Container queries
A container query sizes a component against the box it sits inside, not the viewport. The component is itself declared a container:
.card { container-type: inline-size; container-name: card; }
@container card (inline-size > 30em) {
.card__body { display: grid; grid-template-columns: 1fr 2fr; }
}
This pattern decouples component from page, so the same card looks right whether it appears in a 4-column grid or a sidebar. Container queries reached cross-engine support in Chromium (Blink), WebKit, and Gecko by 2023.
Logical properties
Layout authoring should use logical properties (margin-block-start,
padding-inline, inset-inline-end) rather than physical ones
(margin-top, padding-left, right). Logical properties adapt
automatically to writing-mode and direction, which keeps right-
to-left and vertical scripts working without an extra stylesheet.
Browser engine support
All four primitives are interoperable across Chromium (Blink), WebKit, and Gecko. Where capability matters more than syntax, the recent additions are:
subgrid— interop 2024.- Container queries (
@container) — interop 2023. inline-size/block-sizeand logical insets — interop 2022.aspect-ratio— interop 2021.
Common pitfalls
- Flexbox where grid would fit. Two-axis layouts in nested flex often produce alignment surprises that grid handles by design.
100vwinstead of100%.100vwincludes the scrollbar in most engines and produces horizontal scroll.width: 50%on flex children with content overflow. Usemin-width: 0to prevent intrinsic minimum from preventing shrinkage.- Container queries with
container-type: sizeon prose. Preferinline-sizeto avoid forcing block-size to be defined.
Further reading
- CSS Display Module Level 3 (css-display-3, CR 2023).
- CSS Grid Level 2 (css-grid-2) for
subgrid. - CSS Containment Module Level 3 (css-contain-3) for
@container. - Interop 2024 dashboard reports cross-engine pass rates of 99% (flexbox), 97% (grid), and 95% (container queries) across Chromium, WebKit, and Gecko.
- Una Kravets’s container-query-units demo (2023) is the most direct walk-through of the
cqi/cqb/cqminunits.