The CSS Gap Property
The gap property is one of those CSS features that feels like it should’ve existed forever: it
creates spacing between items in a layout, without you having to sprinkle margins everywhere like
parsley.
In this tutorial, you’ll learn exactly how gap works, where it works (grid, flex,
and friends), and how to debug the classic “why is my gap doing absolutely nothing?” moment.
What gap does (and where it works)
gap sets the spacing between items in a container layout.
-
Grid:
gapcontrols the spacing between grid tracks. -
Flexbox:
gapcontrols the spacing between flex items (including wrapped lines). -
Multi-column layout:
column-gapcontrols spacing between columns (different layout model, similar idea).
Important idea: gap applies to the container, not the items. You add gap on the
parent that has display: grid or display: flex.
gap vs margin: what’s the difference?
You can often fake spacing with margins, but gap is built for layout spacing:
- No “extra space on the edges”: margins can create unwanted spacing at container boundaries unless you carefully remove it for the first/last item.
- Wrap-friendly: with flex-wrap, margins can get weird (especially vertically). Gap is consistent between items and rows.
- Cleaner CSS: spacing is owned by the layout container, not each child.
Think of it like this: margins are an item’s personal space; gap is the container’s seating plan.
Grid gap: the simplest way to see it
Grid is the easiest place to understand gap. We’ll start with a 3-column grid and live-edit the gap
value.
.grid {
gap: 16px;
}
*,
::before,
::after {
box-sizing: border-box;
}
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
padding: 14px;
border: 3px solid #111;
border-radius: 14px;
background: #f6f6f6;
max-width: 720px;
}
.card {
border: 2px solid #111;
border-radius: 12px;
background: #fff;
padding: 14px;
font-family: ui-sans-serif, system-ui, sans-serif;
}
.card strong {
display: block;
margin-bottom: 6px;
}
.card p {
margin: 0;
opacity: 0.85;
}
Card 1Gap adds spacing between items.
Card 2No margins needed.
Card 3Just a container rule.
Card 4Try setting gap to 0.
Card 5Now crank it up.
Card 6Nice and predictable.
Notice how the spacing appears between the cards, but the outer edge of the grid stays tight because we controlled the container padding separately.
row-gap and column-gap (and the 2-value gap shorthand)
Sometimes you want different spacing vertically vs horizontally. That’s what row-gap and
column-gap are for.
-
row-gapcontrols spacing between rows (block direction). -
column-gapcontrols spacing between columns (inline direction). -
The shorthand
gapcan take two values:gap: row column;
Let’s live-edit row-gap and column-gap separately.
.grid {
row-gap: 18px;
column-gap: 30px;
}
*,
::before,
::after {
box-sizing: border-box;
}
.grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
padding: 14px;
border: 3px solid #111;
border-radius: 14px;
background: #f6f6f6;
max-width: 780px;
}
.tile {
border: 2px solid #111;
border-radius: 12px;
background: #fff;
padding: 12px;
font-family: ui-sans-serif, system-ui, sans-serif;
text-align: center;
}
12345678
If you prefer shorthand, the following is equivalent:
-
gap: 18px 30px;means row gap is 18px and column gap is 30px. -
gap: 20px;means both row and column gaps are 20px.
Flexbox gap: yes, it works (and it’s glorious)
Flex layouts often used margin hacks for spacing. With gap, your flex container becomes the single
source of truth for spacing.
In this playground, we’ll use a wrapped flex row and edit the gap live.
.chips {
gap: 12px;
}
*,
::before,
::after {
box-sizing: border-box;
}
.chips {
display: flex;
flex-wrap: wrap;
padding: 14px;
border: 3px solid #111;
border-radius: 14px;
background: #f6f6f6;
max-width: 760px;
font-family: ui-sans-serif, system-ui, sans-serif;
}
.chip {
border: 2px solid #111;
background: #fff;
border-radius: 999px;
padding: 10px 14px;
line-height: 1;
}
HTMLCSSFlexboxGridSelectorsAnimationsLayoutAccessibilityPerformance
Because this container uses flex-wrap: wrap, the items form multiple lines. Gap keeps spacing
consistent between items and between the lines. That’s one of the biggest wins over margin hacks.
Gap works only if you use the right layout mode
Here’s the number one reason people say “gap not working”: they set gap on a container that is
still display: block.
Let’s prove it with radios. Switch the container’s display value and watch what happens.
.container {
display: block;
gap: 18px;
}
*,
::before,
::after {
box-sizing: border-box;
}
.container {
padding: 14px;
border: 3px solid #111;
border-radius: 14px;
background: #f6f6f6;
max-width: 720px;
font-family: ui-sans-serif, system-ui, sans-serif;
}
.container {
grid-template-columns: repeat(3, 1fr);
}
.item {
border: 2px solid #111;
border-radius: 12px;
background: #fff;
padding: 14px;
text-align: center;
}
OneTwoThreeFourFiveSix
When display: block, the children are just normal block elements stacked vertically, and
gap doesn’t create spacing.
Switch to flex or grid and the same gap suddenly becomes meaningful.
Gap and images: a practical card gallery
Let’s do something closer to real life: a responsive card gallery with images.
.gallery {
gap: 20px;
}
.gallery {
gap: 8px;
}
.gallery {
gap: 36px;
}
*,
::before,
::after {
box-sizing: border-box;
}
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
padding: 14px;
border: 3px solid #111;
border-radius: 14px;
background: #f6f6f6;
max-width: 860px;
font-family: ui-sans-serif, system-ui, sans-serif;
}
.card {
border: 2px solid #111;
border-radius: 14px;
overflow: hidden;
background: #fff;
}
.card img {
display: block;
width: 100%;
height: 120px;
object-fit: cover;
}
.card .body {
padding: 12px;
}
.card .title {
font-weight: 700;
margin: 0 0 6px 0;
}
.card p {
margin: 0;
opacity: 0.85;
}
![]()
Mountain
Gap controls spacing between cards.
![]()
City
No need for child margins.
![]()
Forest
Pick a gap that fits the vibe.
![]()
Sea
Try 8px vs 36px.
Click each snippet to activate a different spacing scale. This is a great workflow in real projects: pick a spacing token (8, 12, 16, 20, 24, 32...) and apply it consistently.
Responsive gap with clamp() and CSS variables
You don’t have to keep gap fixed. A common modern pattern is to make spacing responsive:
-
Use
clamp(min, preferred, max)so gap grows with the viewport but stays within limits. - Store it in a CSS variable so multiple components share the same spacing.
.layout {
--space: clamp(10px, 2vw, 28px);
gap: var(--space);
}
.layout {
--space: clamp(4px, 1.2vw, 14px);
gap: var(--space);
}
.layout {
--space: clamp(16px, 3vw, 44px);
gap: var(--space);
}
*,
::before,
::after {
box-sizing: border-box;
}
.layout {
display: grid;
grid-template-columns: 1fr 1fr;
padding: 14px;
border: 3px solid #111;
border-radius: 14px;
background: #f6f6f6;
max-width: 820px;
font-family: ui-sans-serif, system-ui, sans-serif;
}
.panel {
border: 2px solid #111;
border-radius: 14px;
background: #fff;
padding: 14px;
}
.panel strong {
display: block;
margin-bottom: 6px;
}
Left Resize the preview area if your playground supports it.
Right Gap grows within the clamp limits.
Bottom left Same variable, consistent spacing.
Bottom right Try different clamp recipes.
This approach helps spacing feel “designed” across different screen sizes, without writing a bunch of media queries.
Learn more about clamp() in the CSS Clamp Interactive Tutorial.
Animating gap (yes, you can)
Because gap is a length value, it can be animated with transitions. This can be a tasteful micro-interaction, like expanding spacing on hover.
.row {
gap: 10px;
transition: gap 200ms ease;
}
.row:hover {
gap: 26px;
}
*,
::before,
::after {
box-sizing: border-box;
}
.row {
display: flex;
padding: 14px;
border: 3px solid #111;
border-radius: 14px;
background: #f6f6f6;
max-width: 760px;
font-family: ui-sans-serif, system-ui, sans-serif;
}
.block {
border: 2px solid #111;
border-radius: 12px;
background: #fff;
padding: 14px 18px;
}
Hovertochangegap
Tip: if you use lots of motion effects, consider respecting reduced motion preferences with a media query.
“Gap not working” troubleshooting checklist
-
Is the container actually a layout container? You need
display: gridordisplay: flex(or multi-column forcolumn-gap). - Is the gap on the parent? Gap belongs on the container, not the children.
-
Are you expecting space around the outside? Gap only creates space between items.
Use
paddingon the container for outer spacing. - Are you mixing margin and gap? You can, but margins may add extra spacing you didn’t expect. Temporarily remove margins to see what gap is doing.
-
Flex wrapping confusion? If you want multiple lines, you need
flex-wrap: wrap. Gap won’t magically create new rows; it just spaces whatever layout you already have.
Gap quick reference
-
gap: 20px;sets both row and column gaps to 20px. -
gap: 12px 24px;sets row gap to 12px and column gap to 24px. -
row-gap: 16px;controls vertical spacing in grid/flex. -
column-gap: 16px;controls horizontal spacing in grid/flex (and between columns in multi-column layout). -
Gap is best paired with container
paddingto create a clean “outside + inside” spacing system.
Best practices for using gap
- Use consistent spacing steps (like 8, 12, 16, 20, 24, 32).
- Prefer gap over margins for layout spacing inside grids and flex rows.
-
Keep “outer spacing” separate: use container
paddingfor the outside andgapfor the inside. -
If you find yourself adding margins to every child, consider moving that spacing responsibility to the
container with
gap.
That’s the core of the gap property: simple, predictable, and a huge quality-of-life upgrade for
layouts. Happy spacing!
