CSS grid row gap

row-gap sets the space between rows only. It never adds space at the top or bottom of the grid, and it never collapses with margins, which is why it replaced margin-based spacing.

CSS grid row gap is set with row-gap, or with the first value of the gap shorthand, and it controls the space between rows only.

.list {
  display: grid;
  row-gap: 24px;
}

No space is added above the first row or below the last. That is by design, and it is what makes the property safe to use on a container you do not fully control.

A three-row grid with a 24px row gap, edges flush to the container.
A three-row grid with a 24px row gap, edges flush to the container.

The shorthand and its order

gap takes one or two values. Two values mean row first, then column.

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 32px 16px;   /* 32px between rows, 16px between columns */
}

The order matches grid-template-rows before grid-template-columns, and grid-area's row-then-column convention. When in doubt, rows come first in grid.

Written Row gap Column gap
gap: 16px 16px 16px
gap: 32px 16px 32px 16px
row-gap: 32px 32px Unchanged
column-gap: 16px Unchanged 16px
gap: normal 0 in grid 0 in grid

The old names grid-row-gap and grid-column-gap still work as aliases, but the unprefixed versions are the current ones and also apply to flex and multi-column layouts.

Why gap replaced margin-bottom

The margin approach needed a cleanup rule.

/* the old way */
.item { margin-bottom: 24px; }
.item:last-child { margin-bottom: 0; }

That override is fragile. It breaks when items reorder, when one is hidden, or when the list is rendered from data and the last item is conditional.

Gap has no trailing space to remove, so no override is needed. It also survives wrapping: in a wrapped grid the spacing between wrapped rows is the row gap, with no extra rules.

Two lists side by side, one spaced with margin-bottom and one with row-gap, showing the trailing space difference.
Two lists side by side, one spaced with margin-bottom and one with row-gap, showing the trailing space difference.

Gap does not collapse

Adjacent vertical margins in normal flow merge into one. A 20px bottom margin next to a 30px top margin produces 30px of space, not 50px.

Gaps never do this. A 24px row gap is 24px, regardless of what margins the items carry. If an item also has margin-top: 12px, the total becomes 36px, added rather than merged.

For that reason, mixing gap and vertical margins on the same items is the usual source of uneven spacing. Pick one.

Different spacing between specific rows

row-gap is a single value for the whole grid. To vary the spacing, use explicit row tracks with spacer rows, or add padding to the items in the section that needs more air.

.sheet {
  display: grid;
  grid-template-rows: auto 40px auto auto;  /* the 40px row is the spacer */
  row-gap: 12px;
}

The spacer row still gets the 12px gap on each side of it, so the visible separation is 64px. Account for that when choosing the number.

Units worth using

  • rem for vertical rhythm that scales with the root font size. row-gap: 1.5rem stays proportional when a reader enlarges text.
  • px when the gap is a fixed structural value, such as the seam between panels in a dashboard.
  • clamp() when the gap should grow with the viewport. row-gap: clamp(12px, 3vw, 32px) is a common pattern.
  • Percentages resolve against the content box block size, which is often not what people expect. Avoid unless you have set an explicit height.

Gap in flex and columns

The property is not grid-only. It applies to flex containers and multi-column layouts with the same syntax.

.toolbar { display: flex; flex-wrap: wrap; gap: 8px 12px; }
.article { columns: 2; column-gap: 40px; }

In a wrapped flex container the row gap is what separates the lines, which is the main reason gap replaced the margin workarounds there too.

One difference: in multi-column layout, column-gap has an initial value of 1em rather than zero. Setting it explicitly is worth doing so the spacing does not track the font size by accident.

Layout Initial row gap Initial column gap
Grid 0 0
Flex 0 0
Multi-column Not applicable 1em

Gap and grid-template-areas

Named areas and gaps combine without conflict. The gap sits between the tracks, so an area spanning two columns absorbs the gap between them.

.page {
  display: grid;
  grid-template-columns: 240px 1fr;
  grid-template-areas:
    "side main"
    "side foot";
  gap: 20px 32px;
}

The side area spans both rows, so it has no 20px seam through it. Its total height is the two rows plus the row gap, which is what you want for a sidebar that runs the full height.

This is worth knowing because it is the usual reason a spanning element looks taller than the sum of its rows. The gap it absorbed is part of it.

A quick spacing scale

Arbitrary gap values across a document produce spacing that looks almost right and never quite aligns. A small scale fixes it.

:root {
  --s1: 8px;
  --s2: 16px;
  --s3: 24px;
  --s4: 40px;
}
.tight  { row-gap: var(--s1); }
.list   { row-gap: var(--s2); }
.cards  { gap: var(--s3) var(--s2); }
.blocks { row-gap: var(--s4); }

Four steps covers most documents. The rule of thumb is that the gap between groups should be visibly larger than the gap within a group, otherwise the grouping is not readable.

Gaps and separators

A common need is a visible rule between rows rather than blank space. Gap alone cannot draw one, since it is empty by definition.

.list { display: grid; row-gap: 0; }
.list > * + * { border-top: 1px solid rgba(255, 255, 255, .1); padding-top: 16px; }

Set the gap to zero and let border plus padding produce both the line and the spacing. Mixing gap with borders gives a line floating in the middle of the gap, which is usually not the intent.

Checking spacing before you send the page

The spacing test page opened from a share link, gaps rendering as authored.
The spacing test page opened from a share link, gaps rendering as authored.

Spacing problems are easiest to see at a width other than your own. Open the file in the HTML file opener and resize it.

To get a second opinion, paste the HTML into a document and turn it into a link. The reader opens the rendered page, not a file, and the address survives your next spacing adjustment.

Related reading: CSS grid for the track model, CSS grid wrap for responsive columns, and flexbox, which takes the same gap property.

Questions people ask

What is the difference between gap, row-gap and column-gap?

gap is the shorthand. One value sets both directions. Two values set row gap first, then column gap, matching the order of grid-template-rows and grid-template-columns. row-gap and column-gap set one axis each.

Why is there no gap above the first row?

By definition gap sits between tracks, not around them. The grid container has no outer gutter. If you want space at the edges, use padding on the container, which is the correct property for that job.

Do grid gaps collapse like margins?

No. Gaps never collapse and never merge with an adjacent margin. That predictability is the main reason to use gap instead of margin-bottom on children, which required a last-child override to remove the trailing space.

Can row-gap use a percentage?

Yes, and it resolves against the block size of the content box. In an auto-height container this can behave unexpectedly, so fixed lengths or rem units are the safer choice for row spacing.

Keep reading