← All posts

Four Layers of Modern CSS Layout: From Flexbox to Container Queries

A clear map: Flexbox solves one dimension, Grid solves two, Subgrid pierces nesting, Container Queries respond to the container. A single card component walks through each technique's proper boundary.

Summary: Choosing a layout approach isn’t “newer is better” — it’s “use the right layer”. This post walks a blog card component through four modern CSS layout techniques and their trade-offs.

The overview

Technique Dimensions Solves Browser support
Flexbox One Main-axis alignment, even distribution 2015+
Grid Two Rows and columns together 2017+
Subgrid Nested grids Alignment across layers 2023+
Container Queries Container scope Component-level responsiveness 2023+

Layer 1: Flexbox, the one-dimensional Swiss army knife

Flexbox handles “how to arrange things along one line (or column)”. The classic cases are nav bars and card action areas:

.card__actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.card__actions .spacer {
  flex: 1; /* pushes later buttons to the right */
}

Three rules of thumb: gap replaces margins, flex: 1 controls growth/shrink, and min-width: 0 unlocks text truncation.

Layer 2: Grid, real two-dimensional control

When a card list needs rows and columns constrained at once, Grid is the answer:

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 1.5rem;
}

auto-fit + minmax() is the golden combo for responsive grids: the column count derives from container width, no media queries needed.

Layer 3: Subgrid, aligning through nesting

Nested grids share one pain: the inner grid can’t align with the outer tracks. Subgrid fixes it — the inner grid reuses the parent’s tracks directly:

.article-list {
  display: grid;
  grid-template-columns: 1fr 2fr; /* outer tracks */
}

.article-list > .item {
  display: grid;
  grid-template-rows: subgrid; /* rows pierce to the parent */
  grid-row: span 3;
}

Now a set of cards aligns date, title, and excerpt row-by-row no matter how long the content is — pure CSS, zero JS measuring.

Layer 4: Container Queries, the component’s own responsiveness

Media queries respond to the viewport; container queries respond to the container. The same component in a sidebar (narrow) and in the main column (wide) should render differently:

.card {
  container-type: inline-size;
}

@container (min-width: 24rem) {
  .card__body {
    display: grid;
    grid-template-columns: 8rem 1fr;
  }
}

Combined with container-name, you can also target a specific named container — building genuinely reusable “smart components”.

A decision tree

  1. Arranging one line/column? → Flexbox
  2. Rows and columns at once? → Grid
  3. Aligning through nested grids? → Subgrid
  4. Component varies with its host container? → Container Queries
  5. None of the above? → Check whether you need CSS at all; sometimes margin: auto is enough

Wrap-up

Modern CSS layout isn’t a zero-sum game of “Grid replaces Flexbox” — it’s four layers stacking on top of each other. Use the right layer first, then optimize; that stabilizes code quality far better than chasing new features.