Dealing with spacing in compiled markdown articles.
This is one of those web development tasks that starts simple but gets complicated in the process: Applying vertical margins to elements inside an article, for example a blog post that consists of compiled markdown.
Most of the time, you have to deal with exceptions and dependencies: Headlines and images need more whitespace. But if an image follows another image, there should be less whitespace between them. The whitespace between an h2
and an h3
should be larger than between an h2
and a paragraph. And so on.
When I started with web development a couple of years ago, all those exceptions and dependencies always lead to convoluted code, visual inconsistencies and unexpected behavior. I definitely googled »Why does margin-top not work« more than once.
What follows is a technique I’ve now been using for a while. It avoids collapsing margins, offers decent readability and allows for complex dependencies.
A simple article might look like this:
<article class="article">
<h1>Hello World</h1>
<p>Lorem ipsum dolor sit amet</p>
<p>Lorem ipsum dolor sit amet</p>
<img src="…" alt="…">
<p>Lorem ipsum dolor sit amet</p>
<ul><li>Lorem</li><li>Ipsum</li><li>Dolor</li></ul>
</article>
I usually take two paragraphs and adjust the vertical margin between them so that it looks good. Then I use that value as the base margin for all elements.
.article > * + * {margin-top: 1.5rem;}
This rule adds margin-top
to any direct children of .article
that have an adjacent sibling. By only applying margin-top
to direct children, I avoid any unwanted behavior. For instance: The <ul>
in the HTML code above would receive margin-top
, but not its <li>
.
In the CodePen below you can see an example:
The margins that are applied by the CSS rule above can be highlighted.
In the next step I add more specific rules, for instance:
.article > img + * {margin-top: 3rem;}
Any element that follows an img
receives a specific margin-top
. This is similar to applying margin-bottom
to the img
directly. But using the adjacent sibling selector and margin-top
has two advantages: I don’t have to remove margin-bottom
from the :last-child
and avoid collapsing margins.
The CodePen below shows an extended example:
The margins that are applied in this step can be highlighted.
In this step I add rules to specific elements, for instance:
.article > * + h2 {margin-top: 4rem;}
.article > * + img {margin-top: 3rem;}
If an h2
has an adjacent sibling, it receives a specific margin-top
. The same goes for an image with an adjacent sibling.
Extended CodePen example:
The margins that are applied in this step can be highlighted.
In the last step I deal with specific dependencies:
.article > img + img {margin-top: 1rem;}
If an image follows another image, the margin between them should be very small.
Extended CodePen example:
The margins that are applied in this step can be highlighted.
And if you want, you can get even more specific. For instance:
.article > img + img + img + h2 {margin-top: 5rem;}
If an h2
follow three images in a row, it receives a specific margin-top
. Fortunately, this is an edge case. But it’s good to know that the adjacent sibling selector can solve such complex dependencies.
To improve readability, I use (SCSS) nesting and write each rule in one line. I also don’t group selectors with the same value, since CSSO will take care of that in my build task.
.article {> * + * { margin-top: 1.5rem }
> h2 + \* { margin-top: 1rem }
> img + \* { margin-top: 3rem }
> \* + h2 { margin-top: 4rem }
> \* + h3 { margin-top: 3.5rem }
> \* + img { margin-top: 3rem }
> img + img { margin-top: 1rem }
> h2 + h3 { margin-top: 4.5rem }
}
This technique also work well with SASS or CSS variables, for example for baseline grids. If every margin is calculated from a base margin variable, you only have to change that variable to increase or decrease the overall whitespace.
CodePen example using a CSS variable:
The websites we develop at our agency usually have very complex articles. They not only consist of compiled markdown, but also elements such as category titles, intro texts or nested layouts.
Using the adjacent sibling selector and only margin-top
allows me solve complex design requirements while maintaining comprehensible CSS rules. Especially if I have to add or adjust rules later on.