Gap in Flexbox: replacing margin hacks
For years, spacing flex items meant margin tricks and lobotomized owls. The gap property finally gives Flexbox the same clean spacing that Grid has had since day one.
The old margin-hack approach
Before gap worked in Flexbox, the standard technique was to add margin to every item and then use a negative margin on the container to cancel the outer edges. This was fragile, hard to read, and created overflow issues.
/* The old way — negative margin hack */
.card-grid {
display: flex;
flex-wrap: wrap;
margin: -0.5rem; /* cancel outer edges */
}
.card-grid > * {
margin: 0.5rem; /* spacing between items */
flex: 1 1 calc(33.333% - 1rem);
}
/* Problems:
1. Container overflows by 0.5rem on all sides
2. Changing spacing means updating two values
3. :first-child / :last-child selectors for edge cases */
The gap property — clean and simple
gap applies spacing between flex items only — never on the outer edges. No negative margins, no overflow, no edge-case selectors.
/* The modern way */
.card-grid {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.card-grid > * {
flex: 1 1 calc(33.333% - 0.667rem);
}
/* That's it. No negative margins. No overflow.
gap only appears between items, not on edges. */
gap in Flexbox has been supported in all major browsers since April 2021 (Safari 14.1). It is safe for production use.row-gap and column-gap
The gap shorthand sets both row-gap and column-gap. You can set them independently when vertical and horizontal spacing differ.
/* Equal spacing in both directions */
.grid { gap: 1.5rem; }
/* Different row and column spacing */
.grid { gap: 2rem 1rem; }
/* ↑ row-gap: 2rem, column-gap: 1rem */
/* Or set them individually */
.grid {
row-gap: 2rem;
column-gap: 1rem;
}
/* Common pattern: tighter horizontal, looser vertical */
.tag-list {
display: flex;
flex-wrap: wrap;
row-gap: 0.75rem;
column-gap: 0.5rem;
}
Gap interacts with flex-basis calculations
When you use gap with wrapping flex items, the gap space is subtracted from the available space before flex-grow distributes the remainder. This means your flex-basis calculations need to account for the gap.
/* 3 columns with 1rem gap */
.three-col {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
/* Each item's basis must account for 2 gaps across 3 items */
.three-col > * {
flex: 1 1 calc((100% - 2rem) / 3);
}
/* For 4 columns with 1.5rem gap */
.four-col > * {
flex: 1 1 calc((100% - 4.5rem) / 4);
/* 3 gaps × 1.5rem = 4.5rem */
}
The formula is: flex-basis: calc((100% - (columns - 1) * gap) / columns). With flex-grow: 1, items expand to fill any rounding leftovers.
Gap vs margin — when to use each
gap and margin serve different purposes. Use gap for spacing between siblings within a flex or grid container. Use margin for spacing an element relative to its surroundings or for auto-margin alignment tricks.
/* gap: spacing between items in a container */
.nav {
display: flex;
align-items: center;
gap: 1rem;
}
/* margin-inline-start: auto — push item to the far end */
.nav__logout {
margin-inline-start: auto;
}
/* margin-block: spacing between stacked sections */
.section + .section {
margin-block-start: 3rem;
}
/* Do NOT use margin to space flex children.
gap handles it cleanly without edge-case selectors. */
Migrating existing code
If you have existing flex layouts using margin hacks, the migration is straightforward. Remove the negative margin on the container, remove the individual margins on items, and add gap to the container.
/* Before */
.list {
display: flex;
flex-wrap: wrap;
margin: -8px;
}
.list > * {
margin: 8px;
}
/* After */
.list {
display: flex;
flex-wrap: wrap;
gap: 16px; /* double the old margin — gap goes between */
}
.list > * {
/* no margin needed */
}
/* Before (lobotomized owl) */
.stack > * + * {
margin-block-start: 1.5rem;
}
/* After */
.stack {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
margin: 8px creates 16px between them. Set gap to that combined value.