What is the best approach for margins in body text?
By default, browsers add margins both at the top and bottom of elements. That is not a problem because adjacent borders collapse.
However, as we can see in the screenshot below, margins do not collapse when there is a border or padding, which results in excessive whitespace in padded boxes.
h2 {margin-block: 1.5em 1rem;
}, ul {
pmargin-block: 1em;
}
Margins on the bottom
In 2012, Harry Roberts proposed that you should only apply margins in one direction, preferably the bottom. The argument was that margin collapsing is a bit of complexity we just don't need.
This is the general rule I currently use and that is also employed in popular frameworks such as bootstrap. Of course there are exceptions: Headings need a top margin.
For the problem with the padding, Harry recommends to use the :first-child
and :last-child
selectors. Chris Coyier added that there might also be a margin on the last child of the last child (and so on), so you have to do a little more work then that.
All put together, you end up with code like this:
h2 {margin-block: 1.5em 1rem;
}, ul {
pmargin-block: 0 1em;
}.box > :first-child {
margin-block-start: 0;
}.box > :last-child {
margin-block-end: 0;
}
Margins between elements
In 2014, Heydon Pickering proposed that margins should not be defined on elements, but between elements. This can be done by using the adjacent sibling combinator and top margins. The code could look something like this:
, p, ul {
h2margin-block: 0;
}* + h2 {
margin-block-start: 1.5em;
}* + p,
* + ul {
margin-block-start: 1em;
}
One benefit of this approach is that it keeps very low specificity, which is an important property for base styles.
On the other hand, I am not convinced that the basic premise is even correct. Note how there is no margin between the list items in the second list (the one that contains paragraphs). The visual hierarchy is off here because the margin inside of the list items creates a bigger separation than the list items themselves.
You could argue that the list should be in charge of adding margins here. But the list doesn't know whether it contains paragraphs or not. So maybe just looking at siblings isn't enough.
I really like this approach. But because of its limitations I currently only use it in some special cases, e.g. top margins on headings.
flex gap
The previous approaches are both 10 years old. A lot has happened in the meantime, so more options have become available. Specifically, I sometimes hear people say that we should use the gap
property instead of margins. That could look something like this:
.container {
display: flex;
flex-direction: column;
gap: 1em;
}
h2 {margin-block: calc(1.5em - 1rem) 0;
}, ul {
pmargin-block: 0;
}
While I love gap
, I am not 100% convinced it is the right approach in this case. Since it only adds space between elements, it has some of the same issues as the previous approach. Also note how the paragraphs inside list elements do not have any margins because the flex styling is only applied to the box itself.
margin-trim
In 2023, Apple proposed a new CSS attribute called margin-trim
that is supposed to finally fix the problem for good. This would indeed be nice, but so far there seems to be little interest from other vendors.
Conclusion
I had already done all the screenshots when I realized I should have included a nested list. That is another case that frequently produces unexpected results. At least I was able to include it in the codepen I used for all the examples.
It is still hard (impossible?) to produce base styles that just work out of the box for every kind of content and at the same time don't make it extremely complicated to overwrite them for custom components.
If we have to live with some edge cases, I feel like excessive margins at the bottom are more acceptable than excessive margins at the top or missing margins. So I will stick with the bottom margin approach for now.