Line-height and vertical rhythm in CSS

In typography, leading refers to the space between lines of type. But as a Web designer, you’ll probably be better acquainted with line-height, which refers to the distance between baselines of successive lines of type. What are the ins and outs of this line-height property? Besides some background information, I’ll walk through two possible approaches to maintain the vertical rhythm in your document while keeping your CSS clean.

Leading and vertical rhythm

The term leading (which rhymes with “heading”) originates from the time when typesetting was done by hand, and strips of lead were used to increase the whitespace between lines of type. Without added whitespace, bodies of text become more difficult to read.

Optimal line-height depends on several factors. Above all, typeface and measure or, in other words, font-family and line-length. Recall that best legible line-length is about 65 characters, or somewhere between 2 and 3 alphabets. Nevertheless, a starting point for good line-height is somewhere between 120% and 145% of the font-size. Let your eyes be the judge.

Vertical motion — or more specifically vertical rhythm, is the backbone of the structure of a document. As Robert Bringhurst said: Space in typography is like time in music. Your bodies of text should be on-beat for the most harmonious experience. Interruptions like headings can be placed off-beat or even in an irrational rhythm, but bodies of text always have to return to the beat.

leading and line-height on paper

The CSS line-height property

Setting up a vertical rhythm for your document begins with setting the line-height so that your bodies of text are optimally legible. The line-height property can take the following values:

normal
Default – Depending on the browser used, this is the same as setting <number> between 1.0 and 1.4.
<length>
The height is set to this length. The length is the computed value.
<number>
The computed value of the property is this number multiplied by the element’s font-size. However, the number, not the computed value, is inherited.
<percentage>
The computed value of the property is this percentage multiplied by the element’s computed font-size.
none
The computed value of the property is the ancestor block element font-size.

Let me elaborate on the term computed value and furthermore stress on an important aspect of normal and <number>.

Once a user agent has loaded and parsed a document, relative units are first resolved into an absolute pixel value; the computed value. In most cases, elements inherit computed values. However, there are some properties whose specified value may be inherited. This is exactly the case with the <number> value for the line-height property.

The initial browser setting thus makes sure that all text has legible line-height; since it is always proportional to the element’s font-size. There are several other good reasons why normal/<number> is the browser default (more on that at the end of this article). But it offers no straightforward vertical rhythm out of the box!

Technique

The technique that is set forth revolves around setting up a document-wide fixed line-height. The main advantage is that we can have block-level elements with several different font-size without affecting the line-height and thus our vertical rhythm.

To make things clear, I belong to the school of thought that believes that font-sizing should be done primarily in %, em or rem and the body’s font-size property is to be left alone. All these techniques go well together since I use em-based media queries that tie the breakpoints to the content and layout, rather than the browser’s viewport dimensions. For a more in-depth study on this approach I recommend the following article by Filament Group: How we learned to leave default font-size alone and embrace the em.

Setting base rhythm

First we reinstate the default font-size that is used for bodies of text in our document. But primarily, we let all child elements of the body inherit the computed size of 1.25em for the line-height. With a default font-size of 16px, this amounts to a document-wide line-height of 20px, unless overwritten.

html {
 font-size: 100%;
}

body {
 font-size: 1em;
 line-height: 1.25em;
}

Secondly, we will have to set some margins to ensure that everything stays in the rhythm. After we add the following CSS rules, we have a pretty solid base rhythm.

p, ul, ol, dl, blockquote, pre {
 margin-bottom: 1.25rem;
}

h1, h2, h3, h4, h5, h6 {
 margin-bottom: 1.25rem;
}

h1 { line-height: 2.5rem; }
h2 { line-height: 2.5rem; }

Let me explain why I use rem as unit for the margin-bottom property on the above rules. If we had used em and also declare some special class of paragraph to have font-size 1.6rem, the margin of 1.25em would have been calculated from the 1.6rem font-size and had resulted in a bottom margin of 2.0rem. And the rhythm would have been screwed up.

The default font-size for the h1 and h2 headers are (most often) 2em and 1.5em respectively. So we have to double the line-height of these elements so that possible multi-line headers have enough space.

Since IE 8 and Opera Mini don’t support rem, and Android support is still whacky. We can choose to provide some fallback unit or simply use a rem polyfill. The polyfill is my preferred method since it calculates pixel values of the browser default font-size value.

Closing remarks

This article simply sets out my current favored practice. Furthermore, I believe that it represents a straightforward way to structure cascading style sheets. The advantage lies in the fact that you don’t have to recalculate line-height every time you change the font-size of a block-level element. And this arguably results in leaner and easier to maintain CSS.

I am well aware of the fact that there are good reasons for the default line-height to be set at normal. To quote myself: The W3C always seems to have thought about every aspect of Internet documents long before the majority of the Web designer community even conceives of the question.

Getting to the bottom of it

One of the intricate aspects lies in the way the box model works for inline HTML elements. After you have read this and understand the implications — line-height is a minimum value and lines can be pushed apart, we can fix those issues by adding:

/* Prevent inline elements affecting line-height. */
sub,
sup {
  line-height: 1;
}
small {
  line-height: 1;
}

If we hadn’t set the line-height for these inline elements, they would still have been 20px and the relative positioning or the smaller font-size would have forced the lines apart, effectively resulting in a larger line-height for the line these elements are used.