
The CSS gap Property: Spacing Made Simple in Flexbox and Grid
Spacing items evenly sounds like the simplest thing in CSS, yet for a long time it was surprisingly awkward. Put a margin on every item and you get unwanted space on the outer edge. Remove it with :last-child and the trick falls apart the moment items wrap onto a second line. The popular workaround, a negative margin on the container to cancel out the item margins, worked but was confusing to read and easy to break.
The gap property makes all of that unnecessary. It tells a layout container to place space between its children, and only between them: never before the first item, never after the last, and correctly on every row and column when items wrap. This guide covers how gap works in grid, flexbox, and multi-column layouts, the patterns that replace old spacing hacks, and the few edge cases worth knowing.
What gap Does
gap is set on the container, not the children. It defines the size of the gutters between rows and columns.
.toolbar {
display: flex;
gap: 0.75rem;
}
<div class="toolbar">
<button>Bold</button>
<button>Italic</button>
<button>Link</button>
</div>
Each button is separated by exactly 0.75rem. The first button sits flush against the container's start edge and the last against its end edge, with no extra space to cancel out.
The important mental model is that gap space is not attached to any item. It isn't margin. It lives in the layout itself, between tracks or lines. That's why it never leaks onto the outside edges and why it doesn't collapse or combine with other spacing.
The Longhands: row-gap and column-gap
gap is shorthand for two properties:
row-gap: the space between rows.column-gap: the space between columns.
With one value, both are the same. With two, the first is the row gap and the second is the column gap:
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 2rem 1rem; /* 2rem between rows, 1rem between columns */
}
That order, row first then column, matches the order used elsewhere in grid (like grid-template: rows / columns). It's easy to mix up, so if you ever need only one, the longhands are clearer:
.grid {
row-gap: 2rem;
column-gap: 1rem;
}
Legacy grid-gap Names
Early versions of CSS Grid used grid-gap, grid-row-gap, and grid-column-gap. Those names were later generalized to gap, row-gap, and column-gap so they could apply to flexbox and multi-column too. Browsers still accept the old grid- prefixed names as aliases, but there's no reason to write them in new code.
gap in CSS Grid
Grid is where gap started, and it behaves exactly as you'd expect. The gutter sits between every track:
<ul class="product-grid">
<li class="product-card">Canvas tote</li>
<li class="product-card">Linen shirt</li>
<li class="product-card">Wool scarf</li>
<li class="product-card">Leather belt</li>
<li class="product-card">Cotton cap</li>
<li class="product-card">Denim jacket</li>
</ul>
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
gap: 1.5rem;
padding: 0;
list-style: none;
}
Grid takes the gaps into account before distributing fr space. If the container is 1000px wide with three columns and two 24px gaps, each 1fr column gets (1000 - 48) / 3 pixels. You don't have to do any of that math yourself, which is one of the biggest wins over the old approach of using percentages and calc().
Gaps also appear between implicit tracks, the rows and columns grid creates automatically when items overflow the explicit grid. And if an item spans several tracks, it spans the gaps between them too.
gap in Flexbox
Flexbox gained gap support later than grid, and it's now supported in all current major browsers. It works a little differently because flexbox is one-dimensional.
For a single-line flex container, only column-gap matters in a row direction (or row-gap in a column direction). The gap shorthand sets both, and the browser uses whichever applies:
.nav-links {
display: flex;
gap: 1.25rem;
}
Where flex gap really shines is with wrapping:
<ul class="tag-list">
<li>css</li>
<li>layout</li>
<li>flexbox</li>
<li>grid</li>
<li>spacing</li>
<li>responsive</li>
<li>design-systems</li>
</ul>
.tag-list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem 0.75rem;
padding: 0;
list-style: none;
}
.tag-list li {
padding: 0.25rem 0.75rem;
border-radius: 999px;
background: #e0f2fe;
color: #075985;
font-size: 0.875rem;
}
The column gap separates tags on the same line, and the row gap separates the wrapped lines. When the container narrows and a tag moves to a new line, nothing sticks out on the edges. This was nearly impossible to do cleanly with margins.
How Flex gap Interacts with Sizing
In flexbox, gaps are subtracted from the available space before items grow or shrink. So a row of three items with flex: 1 and a 1rem gap still fills the container exactly, with each item getting a third of what's left after the gaps.
That also means gap can contribute to overflow. If items have fixed widths that already fill the container, adding a gap pushes the total past 100%. In a non-wrapping container, items shrink to compensate (if flex-shrink allows it); otherwise, they overflow.
A common mistake is sizing wrapping flex items with percentages:
/* This wraps to two per row instead of three */
.cards {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.cards > * {
flex: 0 0 33.333%;
}
Three items at 33.333% plus two 1rem gaps is more than 100%, so the third item wraps. Account for the gaps in the basis:
.cards > * {
flex: 0 0 calc((100% - 2 * 1rem) / 3);
}
Or, more simply, use grid for anything that's really a grid.
gap in Multi-Column Layout
Multi-column layout has had column-gap since long before flexbox and grid. It sets the space between text columns:
.article-body {
columns: 3 16rem;
column-gap: 2.5rem;
column-rule: 1px solid #cbd5e1;
}
column-rule draws a line in the middle of each column gap. The rule takes no space of its own: if it's wider than the gap, it overlaps the columns rather than widening the gutter. The default column-gap in multi-column layout is 1em, while in flex and grid it's 0.
For a long time, row-gap had no effect in multi-column, because columns were only laid out in one row. Newer specs add wrapping rows of columns with a row gap, but that work is still early and not something to rely on.
Replacing Old Spacing Hacks
If you're maintaining older code, here's how the old patterns map to gap.
The :last-child Margin Reset
/* Before */
.menu li {
margin-right: 1rem;
}
.menu li:last-child {
margin-right: 0;
}
/* After */
.menu {
display: flex;
gap: 1rem;
}
The Negative Margin Wrapper
/* Before */
.grid {
display: flex;
flex-wrap: wrap;
margin: -0.5rem;
}
.grid > * {
margin: 0.5rem;
}
/* After */
.grid {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
The negative margin version also had a side effect: the container's content box was larger than its visible area, which could cause horizontal scrollbars on full-width layouts. gap doesn't have that problem.
The Owl Selector for Vertical Rhythm
The "lobotomized owl" selector, * + *, adds margin to every item except the first:
.stack > * + * {
margin-block-start: 1rem;
}
This is still a useful pattern for flowing content like article text, where different elements should get different spacing and margin collapsing is actually helpful. But for a stack of components, a flex column with gap is simpler:
.stack {
display: flex;
flex-direction: column;
gap: 1rem;
}
One practical difference: with gap, a child whose display is none doesn't leave an empty gap behind, since it isn't a flex item at all. With the owl selector, hiding the first child leaves the second one with a top margin it no longer needs, because * + * still matches it. On the other hand, an element that's present but empty, like an empty div, still gets gaps on both sides in a flex or grid container, so remove empty wrappers rather than leaving them in the markup.
Responsive and Fluid Gaps
gap accepts any length or percentage, so you can make it fluid:
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
gap: clamp(1rem, 3vw, 2.5rem);
}
The gutter grows with the viewport but stays within sensible bounds. You can also tie gaps to a design token:
:root {
--space-s: 0.75rem;
--space-m: 1.25rem;
--space-l: 2rem;
}
.card-list {
display: grid;
gap: var(--space-m);
}
@media (width >= 60rem) {
.card-list {
gap: var(--space-l);
}
}
Percentage Gaps
Percentage gaps resolve against the container's content box size in the relevant dimension. column-gap: 5% is 5% of the container's width. For row-gap, the percentage resolves against the container's height, and if that height is auto, percentages may not resolve the way you'd hope. Stick with lengths or clamp() for row gaps.
Styling the Gap Itself
A long-standing limitation of gap is that you can't style it. It's empty space, and there's been no way to draw lines in grid or flex gutters without extra elements or background tricks.
That's changing. The CSS Gap Decorations spec extends column-rule to grid and flexbox and adds row-rule, so you can draw separators in gaps directly:
.stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 2rem;
column-rule: 1px solid #cbd5e1;
row-rule: 1px solid #cbd5e1;
}
At the time of writing, gap decorations are rolling out in Chromium-based browsers, and support in other engines is still in progress. Check caniuse before relying on it, and use @supports to add a fallback:
.stats > * {
border-inline-start: 1px solid #cbd5e1;
}
@supports (row-rule: 1px solid) {
.stats > * {
border-inline-start: none;
}
}
Until then, the traditional trick for grid lines is a background on the container with the gap set to 1px, letting the background show through:
.table-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1px;
background: #cbd5e1;
border: 1px solid #cbd5e1;
}
.table-grid > * {
background: #ffffff;
padding: 0.75rem;
}
This works well as long as every cell is filled; empty cells at the end of the grid show the background color.
Common Pitfalls
Setting gap on the children. gap belongs on the flex or grid container. On a regular block element, it does nothing.
Expecting gap on a block container. A plain div with display: block ignores gap. It only applies to flex, grid, and multi-column containers.
Mixing gap with justify-content: space-between. They combine. space-between distributes leftover space after gaps are accounted for, so items are always at least gap apart and may be further. That's usually fine, but if you expected exactly the gap value between items, remove the justify-content.
Double spacing during a migration. If you add gap to a container but forget to remove old item margins, you get both. Search for the old margin rules when you switch.
Browser Support
gap in grid and multi-column layouts has been supported everywhere for years. Flexbox gap is supported in all current versions of Chrome, Edge, Firefox, and Safari, and has been for several years now; only very old Safari versions lack it. For nearly every site, you can use it without a fallback. Gap decorations are the only part of this topic that's still new and limited in support.
Conclusion
gap is one of the simplest properties in modern CSS, and one of the most useful. It removes a whole category of spacing hacks, handles wrapping correctly, and makes layout math easier because the browser subtracts gutters before sizing tracks and items. Set it on the container, think of it as space between items rather than around them, and let flexbox and grid do the rest.
If you're still writing :last-child margin resets or negative margin wrappers, those are good candidates to replace the next time you touch that code.


