Field Guide / intermediate

Grid vs Flexbox: choose by behavior, not habit

Use the layout model that matches the relationship between items, then combine Grid and Flexbox where each one owns a clear job.

Live demo

Layout decision helper

Open demo page
CSS used in this demo
.tool {
  display: grid;
  gap: 1.25rem;
  max-width: 48rem;
  margin: 0 auto;
  padding: 1.5rem;
}

.tool h1 {
  margin: 0;
  font-size: 2rem;
  line-height: 1.1;
}

form {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
}

label {
  display: grid;
  gap: 0.35rem;
  font-weight: 700;
}

select {
  min-height: 2.75rem;
  border: 2px solid #171717;
  border-radius: 6px;
  background: #ffffff;
  padding-inline: 0.75rem;
}

.result {
  display: grid;
  gap: 0.5rem;
  border: 2px solid #171717;
  border-radius: 8px;
  background: #e8f3f3;
  padding: 1.25rem;
}

.result__label {
  color: #0a6a6c;
  font-size: 0.78rem;
  font-weight: 800;
  text-transform: uppercase;
}

.result h2 {
  margin: 0;
  font-size: 2.4rem;
  line-height: 1;
}

Most teams do not have a Grid problem or a Flexbox problem. They have a layout relationship problem. The fastest way to make the right choice is to name the relationship before writing the declaration.

If rows and columns both matter, start with Grid. If the layout distributes items along one axis, start with Flexbox. If a child layout must align to tracks owned by a parent, consider Subgrid. If the component needs to change based on its allocated space instead of the viewport, add container queries.

The decision test

Ask what would make the layout wrong if the content changed.

  • If cards must line up in both rows and columns, you need Grid.
  • If buttons should wrap and keep natural widths, Flexbox is usually enough.
  • If a nested heading and body text must align with sibling cards, Subgrid can remove duplicate track math.
  • If the same component appears in a sidebar and a main column, viewport media queries may be the wrong boundary.

This matters because layout bugs often come from solving the wrong relationship. A toolbar written with Grid can become rigid. A dashboard written only with Flexbox can lose column alignment. A reusable card tuned with viewport breakpoints can be wrong in every container except the one where it was designed.

Grid is for shared structure

Grid is strongest when the parent owns a two-dimensional structure. You can name areas, size tracks, align items, and let auto-placement fill predictable slots.

.dashboard {
  display: grid;
  grid-template-columns: minmax(14rem, 18rem) minmax(0, 1fr);
  grid-template-areas:
    "nav header"
    "nav main";
  min-block-size: 100svh;
}

.dashboard__main {
  grid-area: main;
  min-inline-size: 0;
}

The minmax(0, 1fr) detail is not decoration. It tells the flexible track that it is allowed to shrink below the min-content size of its children. Without it, a long table, code sample, or unbroken URL can push the layout wider than the viewport.

Flexbox is for distribution

Flexbox is strongest when items need to size from their content and distribute remaining space along one axis.

.toolbar {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
  align-items: center;
}

.toolbar__spacer {
  margin-inline-start: auto;
}

This works because the toolbar is not asking items to share vertical tracks. It is asking them to sit in a row, wrap when needed, and preserve natural button sizes. The spacer pushes secondary actions away without inventing extra columns.

Combine them deliberately

Most production interfaces should use both.

.article-card {
  display: grid;
  grid-template-rows: auto 1fr auto;
  gap: 1rem;
}

.article-card__meta {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  align-items: center;
}

The card uses Grid because its vertical regions matter: heading, body, actions. The metadata uses Flexbox because it is a one-axis cluster. That separation keeps the component readable: Grid owns structure, Flexbox owns distribution.

Do not ask one layout mode to do every job. A Grid toolbar can work, but every new action may require a track decision. A Flexbox dashboard can work, but every aligned region becomes a negotiation between wrapped lines. If the CSS reads like a workaround, the layout relationship may be assigned to the wrong primitive.

The production rule

Choose the primitive that describes the invariant. If the invariant is “these columns align,” use Grid. If the invariant is “these controls flow together,” use Flexbox. If the invariant is “this nested content inherits parent tracks,” use Subgrid. If the invariant is “this component responds to its own width,” use a container query.

Keep source order meaningful before layout enters the conversation. Grid and Flexbox can both make visual order diverge from document order, but that power should not be used to paper over bad markup.

Debug by changing the content

When the choice is unclear, stress the layout. Add a long heading, remove an image, add one more action, translate a label, and narrow the container. The primitive that still describes the relationship is usually the right one. If a Flexbox row needs several fixed widths and spacer elements to preserve column alignment, Grid is probably the better owner. If a Grid layout uses many one-off areas only to place a row of buttons, Flexbox is probably enough.

Also check where the component will be reused. A layout that is correct in a full-width page section may fail in a sidebar because its breakpoint is tied to the viewport. In that case, the real choice may be Grid plus a container query, not Grid versus Flexbox alone.

The best production CSS often combines primitives in layers: Grid for page and card structure, Flexbox for small clusters, Subgrid for nested alignment, and normal flow for prose. The decision is not tribal. It is about assigning each relationship to the primitive that makes the next maintenance change obvious.

Related guides: Subgrid, Container queries, and Intrinsic sizing.

References