During a recent code review, a colleague suggested using gap instead of individual margins inside flex and grid containers. I hadn’t thought about it before, but after applying it to a real component, the difference was immediately clear: the layout became cleaner, easier to maintain, and more predictable.
In this post, I’ll walk through the refactor, explain why it improves maintainability, and highlight how adopting small, thoughtful patterns can make a big difference at scale.
The Original Approach
Here’s a simplified version of how spacing was originally handled in a card layout:
export const CardTitle = styled(Text)`margin-top: 2rem;`;
export const CardDescription = styled(Text)`
margin-top: 0.5rem;
`;
export const Button = styled(ButtonBase)`
margin-top: 1.5rem;
`;
Each element controlled its own spacing using margin-top.
At first glance, this works. But the more I looked at it, the more a few issues stood out.
What Started to Feel Off
1. Spacing is scattered
Each element is responsible for its own spacing, which makes it harder to understand the layout at a glance.
You end up thinking:
Why is this 2rem? Why is this 0.5rem?
Instead of seeing a consistent system.
2. It doesn’t scale well
If you add another element between the title and description, you now have to rethink spacing again.
Spacing becomes tied to specific elements, not the layout itself.
3. Small edge cases start creeping in
Things like:
- Removing margin from the first or last item
- Inconsistent spacing between elements
- Overriding styles when layouts change
Nothing major individually, but it adds up.
The Refactor: Let the Container Handle Spacing
Instead of letting each child define spacing, I moved that responsibility to the parent using gap.
export const Card = styled.div`
display: flex;
flex-direction: column;
gap: 1rem;
`;
Then grouped related text elements:
export const CardText = styled.div`
display: flex;
flex-direction: column;
gap: 0.5rem;
`;
And removed margins entirely from the children:
export const CardTitle = styled(Text)``;
export const CardDescription = styled(Text)``;
export const Button = styled(ButtonBase)``;
Why This Change Matters
1. Centralized spacing
All spacing logic lives in the container, so adjustments are easier and more predictable.
2. Predictable layouts
No more worrying about first/last child spacing, margin collisions, or one-off overrides.
3. Scales naturally
Add a new element, and spacing is applied automatically. This reduces repetitive work and potential bugs.
4. Simplified mental model
Before: each element decides its own spacing
After: the container decides spacing between children
This shift makes layouts easier to reason about for yourself and for teammates reviewing your code.
Using Gap with Grid
This pattern became even clearer when applied to the grid layout:
export const CardGrid = styled.div`
display: grid;
gap: 3rem;
`;
Instead of worrying about margins between cards, the grid handles spacing automatically.
No extra logic, no edge cases — just consistent spacing.
When Margin Still Makes Sense
I don’t think this means “never use margin.”
Margin still works well for:
- Spacing between unrelated components
- Layout adjustments outside of flex/grid
- One-off positioning tweaks
But inside a flex or grid container, gap feels like the better default.
Takeaways
- Prefer
gapovermargininside flex and grid containers - Let containers control spacing, not individual elements
- Group related elements to simplify layout logic
- Small CSS decisions can make a big difference in maintainability
This is one of those patterns I wish I had started using earlier. This small refactor reminded me how powerful simple patterns can be. As systems grow, the little decisions — like letting containers handle spacing — add up to fewer bugs, faster reviews, and more maintainable code.
I’ll definitely be applying gap in future components, and I hope this pattern helps your team too.
It simplifies spacing, reduces small layout decisions, and makes components easier to scale over time.