Why gap is almost always the better choice for spacing between items
Setting spacing between flex items with margin almost always means undoing the edge effect at the outer boundaries afterward with a selector like :last-child. The gap CSS property fixes this at the root: it produces space exclusively between items, never at the container's outer edges, making one of the most frequently repeated CSS hacks unnecessary.
Table of Contents
- 1. The classic margin problem between flex items
- 2. gap in flexbox: how it actually works
- 3. Why the 'last child without margin' selector becomes obsolete
- 4. row-gap and column-gap with flex-wrap
- 5. Browser support history: when gap became available in flexbox
- 6. Edge cases: where margin is still needed despite gap
- 7. Using gap and justify-content: space-between together
- 8. Maintainability: gap is more robust against dynamic item lists
- 9. Practical example: a navigation bar built entirely with gap
- 10. Summary
- 11. FAQ
1. The classic margin problem between flex items
Anyone who traditionally sets spacing between flex items with margin-right on every item automatically produces unwanted space after the last item too, because margin knows nothing about neighboring elements, it gets assigned to each element independently. That extra outer space at the container edge shifts subsequent elements, breaks center alignments, and therefore has to be explicitly corrected again.
For years, the classic fix was the selector .item:last-child { margin-right: 0; }, which specifically carves out the edge case. That rule works, but it is an extra, easily forgotten puzzle piece that has to be remembered again for every new flex component, and it quickly points at the wrong element in dynamically sorted or filtered lists once the order changes at runtime.
2. gap in flexbox: how it actually works
The gap property, set on the flex container, produces space exclusively in the gaps between direct flex items, never between the first item and the container's left edge or between the last item and the right edge. That restriction to the inner gaps is exactly the behavior a margin hack had to painstakingly recreate, guaranteed here directly by the specification.
A single gap: 16px; on the container is enough to produce consistent spacing between any number of items, regardless of how many elements actually get rendered. If an application adds another item at runtime or removes one, the spacing stays correct automatically, with no extra line of CSS needed for the new edge case.
.toolbar {
display: flex;
gap: 16px; /* space only between items, never at the outer edges */
}
.toolbar__item {
/* no margin needed at all for spacing between items */
}
3. Why the 'last child without margin' selector becomes obsolete
Comparing the margin solution against the gap solution shows the difference most clearly: the margin variant needs two rules, one for the default spacing and one for the exception at the last element, while the gap variant gets by with a single line on the container and needs no exception rule at all.
That disappearing exception rule is more than cosmetic: it removes an entire class of bugs that arise when a :last-child selector suddenly no longer targets the actually visible last element, because of dynamically inserted wrapper elements, markup comments, or conditionally rendered items. gap never runs into this problem in the first place, because it never targets a single element deliberately.
/* Before: margin hack with an exception rule */
.toolbar__item { margin-right: 16px; }
.toolbar__item:last-child { margin-right: 0; }
/* After: a single line on the container, no exception needed */
.toolbar { display: flex; gap: 16px; }
4. row-gap and column-gap with flex-wrap
Once a flex container with flex-wrap: wrap; wraps items across several lines, gap distinguishes between row-gap for the spacing between rows and column-gap for the spacing between items within a row. The shorthand gap: 16px 24px; sets both values at once, row spacing first, column spacing second, exactly like margin shorthands.
That distinction does not exist at all with a pure margin approach: a single margin-right can never represent different spacing for horizontal and vertical gaps without also setting margin-bottom and fighting exception selectors for the last row all over again. In multi-row flex layouts, gap is therefore not just more convenient, it is structurally the only way to control both directions independently without edge artifacts.
.tag-list {
display: flex;
flex-wrap: wrap;
gap: 12px 20px; /* row-gap: 12px, column-gap: 20px */
}
5. Browser support history: when gap became available in flexbox
gap was originally specified only for CSS Grid and became usable there in production well before flexbox. Firefox was the first browser to also implement gap for flexbox, while Chrome and Safari only caught up in 2021, Chrome starting with version 84, Safari starting with version 14.1. Anyone who still has to guarantee support for older Safari versions should know this specific version boundary.
For the vast majority of today's projects targeting current browser versions, that gap is by now historical and no longer a practical obstacle. For projects with a guaranteed need to support very old Safari versions, an @supports (gap: 1rem) block that keeps the classic margin approach ready as a fallback within its own section is preferable to using gap unchecked.
.toolbar__item { margin-right: 16px; }
.toolbar__item:last-child { margin-right: 0; }
@supports (gap: 1rem) {
.toolbar__item { margin-right: 0; }
.toolbar { gap: 16px; }
}
6. Edge cases: where margin is still needed despite gap
gap applies the same, uniform spacing to every gap and has no way to deliberately push a single item further from its neighbor. If, say, a horizontal toolbar needs a visually set-apart action, like a logout button, to get noticeably more space from the rest of the group than the other items have between each other, margin-left: auto; or an additional, targeted margin value on exactly that item remains the right solution.
The classic pattern of pushing an element to the right edge of a flex container with margin-left: auto; while the remaining items stay on the left also only works through margin, because gap exclusively produces uniform gaps and cannot express shifting a single element relative to the container.
7. Using gap and justify-content: space-between together
When you combine gap with justify-content: space-between;, the browser adds the gap value on top of the spacing computed by space-between instead of replacing it. That can produce larger gaps than intended when a team uses both mechanisms unreflectively at the same time, because both independently try to enlarge the same gap.
In practice, either gap alone with justify-content: flex-start;, or space-between alone without an additional gap, is enough for most evenly distributed layouts. Anyone wanting to combine both, for example to guarantee a minimum gap between items that space-between otherwise would not deliver with only a few items, should deliberately check the visual result across different item counts.
8. Maintainability: gap is more robust against dynamic item lists
For lists whose content changes at runtime, for example tags a user adds and removes, or search results that get reloaded, gap stays correct regardless of the current item count, because the spacing rule never references a specific element like the last or first one. A margin hack with :last-child, on the other hand, has to be re-evaluated on every list change, which the browser does automatically, but only as long as the CSS selectors actually still match the true last element.
That robustness makes gap especially valuable in component libraries reused by different teams in different contexts: a team that prepends additional, conditionally rendered items to the list does not need to worry about adjusting any margin rules, because gap works independently of the markup's internal structure.
.chip-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
/* correct spacing regardless of how many .chip elements
get added or removed at runtime -- no :last-child needed */
}
9. Practical example: a navigation bar built entirely with gap
A typical horizontal navigation bar with a logo, several menu items, and a right-aligned call-to-action button can be cleanly split using gap for the even spacing between menu items and margin-left: auto; exclusively for the right-set-apart element: gap takes care of the always-uniform gaps, margin takes care of the deliberate exception.
That combination shows the practical ideal: gap as the default tool for uniform spacing, margin only where a single element genuinely needs to be treated differently from the rest of the group, no longer as a general-purpose solution for every gap between sibling elements.
| Task | Margin approach | gap approach | Result |
|---|---|---|---|
| Spacing between items | margin-right on every item | gap on the container | gap needs no exception rule |
| Last item without edge spacing | :last-child { margin-right: 0; } | Automatically correct | gap has no concept of container edges |
| Multi-row spacing | margin-right and margin-bottom combined | gap: row-gap column-gap | gap cleanly separates both directions |
| Deliberately setting one element apart | margin-left: auto on the element | Not possible with gap alone | margin remains the right choice here |
Mironsoft
Modern CSS, layout architecture and rendering performance
CSS that stays maintainable instead of breaking with every change?
We review existing stylesheets for specificity chaos and layout thrashing, then build a CSS architecture with cascade layers, custom properties and modern layout primitives that still makes sense after the tenth feature.
CSS Audit
Systematically uncovering specificity issues, cascade conflicts and unused selectors.
Architecture Refactoring
Introducing cascade layers, custom properties and design tokens cleanly.
Performance Tuning
Fixing layout thrashing, expensive selectors and rendering bottlenecks.
10. Summary
Flexbox gap vs. Margin: The Essentials at a Glance
Core idea
gap produces spacing exclusively between flex items, never at the container's outer edges.
Rule that disappears
The selector .item:last-child { margin-right: 0; } becomes entirely obsolete with gap, no exception needed anymore.
Support boundary
Fully supported since Chrome 84, Firefox 63, and Safari 14.1, check @supports (gap: 1rem) for older browsers.
Remaining edge case
Deliberately setting one element apart (e.g. margin-left: auto) still only works with margin, not with gap.