Home CSS

CSS layout primitives: flow, flex, grid, container

Modern CSS layout is built from four primitives: normal flow, flexbox, grid, and container queries. Knowing which to reach for is the largest determinant of layout quality.

The four primitives

CSS layout in 2025 is dominated by four mechanisms. Each has a preferred use case; mixing them inappropriately produces brittle layouts.

PrimitiveMental modelBest for
FlowBlock-level boxes stack; inline content runs along the writing axis.Prose, default vertical rhythm.
FlexA one-dimensional axis where children distribute, align, and grow.Single-axis distribution: nav bars, button groups.
GridA two-dimensional track system with named lines and areas.Page-level layouts, complex two-axis arrangement.
Container queriesSizing 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-size and 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.
  • 100vw instead of 100%. 100vw includes the scrollbar in most engines and produces horizontal scroll.
  • width: 50% on flex children with content overflow. Use min-width: 0 to prevent intrinsic minimum from preventing shrinkage.
  • Container queries with container-type: size on prose. Prefer inline-size to avoid forcing block-size to be defined.

Further reading