Implementing baseline rhythm in CSS

Share this article

Implementing baseline rhythm in CSS

Vertical rhythm is a typographic concept that’s so often misunderstood by front-end engineers.

By aligning type to a vertical grid, designers can make their work look harmonious and clean. And by having the same visual rhythm implemented correctly, front-end architects can achieve consistent, good-looking results more easily and in shorter time. All that without the need for designers’ input in the process.

In this article, I’ll help you lay foundations for the proper implemenation of a vertical rhythm in CSS. Let’s start by clarifying what we won’t be working on.

The concept has been present on the web for years. It introduces a common line height value (or its multiple) that’s used for all elements, including their paddings and margins, occasionally taking border widths into the equation.

In that scenario, according to the CSS standard, the type gets vertically aligned in the middle of two grid lines. But insert two differently formatted elements next to each other and they’ll seem out of phase.

There is an alternative approach that provides consistent results regardless of the used font settings. That’s aligning the baseline of the text. By using this technique, all type—regardless of its size—lies on the same grid line.

CSS doesn’t provide any handy tools for that, so we’ll have to make them manually using a few tweaks. There are two things to find out:

How far the content has to be shifted.

How to efficiently shift it by that amount.

Determining the shift

In short, the height of a capital letter above the baseline is called the cap height (and that’s what browsers will center out between the grid lines automatically). What we need to do is to shift it by half the difference between line height and cap height.

A cap height is a property of the font that’s being used. It can be determined experimentally by fiddling with the values until our type is properly aligned with the grid.

Solution 2. Use positive top padding and negative bottom margin.

The top padding shifts the content just as we need. The negative bottom margin is used to compensate for this offset. It’s important to always use margins in one direction only, e.g. only bottom margins. Otherwise they collapse and break the whole system.

The biggest drawback of this solution is how it quickly adds complexity. Take the case from our our app, where you’ll find utility padding classes: pt-1 adding 24px top padding, pt-2 48px (two line heights), and so on. Using these classes together either requires involving additional HTML containers or results in overuse of Sass features for generating all possible cases. Both cases make the work cumbersome and impossible to phase out easily later on.

Solution 3. Use positive top margin and negative bottom margin.

As in the previous approach, the top value—margin this time—is compensated with negative bottom margin.

What about collapsing margins?

Fortunately, there’s a neat trick passing by this issue. But first, let me remind how collapsing works in presence of positive and negative margins:

Given two positive margins, the bigger one wins. For margin-bottom:30px and then margin-top:20px, the final space between these elements is 30px.

Given two negative margins, again, the lower (meaning the more negative) wins.

Given a positive and a negative margin, they sum up. For margin-bottom:30px and margin-top:-20px, they result in 10px of whitespace.

The last point means that if we always alternate positive and negative margins, their values will be summed and our type will stay aligned to the grid lines.

To maintain this guarantee, we decided not to use margins anywhere in our app with the expection for the rhythm system. It’s not a big loss, though—uncurated margins aren’t always predictable anyway.

Overcoming margin overflow

Margins in CSS have one more nasty feature: if an element doesn’t have a border nor padding, and its first child has a margin, that margin will flow out of the parent. This becomes an issue when the parent has a background. That background will start wherever the child appears, and not within the parent.

There are two ways to go about it.

Either use overflow:hidden to force the margins to be contained within the parent,

or add padding-top:0.1px, which is a trivial hack. The value is too small to be actually rendered, but it’s enough to keep the child within the parent container bounds.