ESC

Type to search the knowledge base.

Gap vs Margin in Layouts

Prefer gap in flex/grid for inter-item spacing — no collapse surprises, no last-child hacks, and when margin is still the right tool.

beginner3 min read
  • css
  • gap
  • margin
  • layout

For years, flex and grid spacing meant margins on children plus :last-child resets or lobotomized owl selectors. gap puts spacing on the container between items (and tracks). Less CSS, fewer edge bugs, clearer intent.

Docs: MDN gap, Flexbox gap, Grid gap.

gap on the container

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

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: 1rem 1.25rem; /* row column */
}

gap only creates space between items/tracks — not outside the first/last item. Outer padding still belongs on the container or a parent.

.panel {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  padding: 1rem; /* outer inset */
}

Margin pain that gap removes

/* Old pattern */
.list > * + * {
  margin-top: 1rem;
}

/* Or */
.card {
  margin-right: 1rem;
}
.card:last-child {
  margin-right: 0;
}

Problems:

  • Forgetting the last-child reset
  • Margins collapsing in block flow (not with flex/grid items the same way, but mixed layouts confuse)
  • Asymmetric spacing when wrapping
  • Negative margin tricks to cancel grid gutters
/* Modern */
.list {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

When margin still wins

Need Tool
Space between flex/grid siblings gap
Push one item away (margin-inline-start: auto) margin
Overlap / pull (margin-top: -0.5rem) margin
Space outside a component relative to random neighbors margin (or parent gap)
First-line indent / float tricks margin
.nav {
  display: flex;
  gap: 1rem;
}

.nav__auth {
  margin-inline-start: auto; /* absorbs free space — not a gap job */
}

Stacking with nested layouts

.card {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding: 1rem;
}

.card__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

Parent gap + child gap compose cleanly. Parent gap + child margins often double-space.

Gap and percentage / wrapping

gap is accounted for when sizing flex/grid tracks. If columns suddenly wrap “too early,” check gap + padding + borders — not only minmax.

Interview out-loud

“gap sets spacing between items in flex and grid containers without last-child hacks. It doesn’t replace padding for outer insets or auto margins for pushing items. I default to gap for lists, toolbars, and grids, and use margin for positioning within free space or for relationships outside a flex/grid context.”

Footguns

  1. Putting gap on a non-flex/grid container (ignored for normal block).
  2. Using margin and gap for the same relationship.
  3. Expecting gap to collapse like block margins.
  4. Forgetting older browser requirements if you still support them — know your baseline.
  5. row-gap/column-gap typos when only one axis should space.

Migrating a legacy list

/* Before */
.menu a { display: block; margin-bottom: 0.5rem; }
.menu a:last-child { margin-bottom: 0; }

/* After */
.menu {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

Same visual, fewer edge cases when items are conditionally rendered (no last-child thrash). For inline chips that used horizontal margins, flex + gap also removes the doubled gap problem when wrapping.

Gap in multi-column and masonry-like grids

Grid gap applies between tracks. If you previously used negative margin tricks to cancel outer gutters, delete them — gap never adds outer space. Outer inset is padding on the container or margin on the container relative to its parent.

Further reading

Related guides