Why does unitless line-height behave differently from percentage or em in this example?

HtmlCss

Html Problem Overview


I'm perplexed by the behavior of the following CSS, also illustrated in this fiddle.

<style type="text/css">
p {
    font-size: 14px;
}

.percentage {
    line-height: 150%;
}

.em-and-a-half {
    line-height: 1.5em;
}

.decimal {
    line-height: 1.5;
}

.smaller {
    font-size:50%;
}

.caption {
    font-weight: bolder;
    font-size: 80%;
}

</style>

<p class="caption">default line spacing</p>

<p>This tutorial provides a brief introduction to the
programming <span class="">language</span> by using one of its picture-drawing
libraries. This tutorial provides a brief introduction to the programming language. This tutorial provides a brief introduction to the programming language.</p>

<p>This tutorial provides a brief introduction to the
programming <span class="smaller">language</span> by using one of its picture-drawing
libraries. This tutorial provides a brief introduction to the programming language. This tutorial provides a brief introduction to the programming language.</p>


<p class="caption">line-height: 150%</p>

<p class="percentage">This tutorial provides a brief introduction to the
programming <span class="">language</span> by using one of its picture-drawing
libraries. This tutorial provides a brief introduction to the programming language. This tutorial provides a brief introduction to the programming language.</p>

<p class="percentage">This tutorial provides a brief introduction to the
programming <span class="smaller">language</span> by using one of its picture-drawing
libraries. This tutorial provides a brief introduction to the programming language. This tutorial provides a brief introduction to the programming language.</p>


<p class="caption">line-height: 1.5em</p>

<p class="em-and-a-half">This tutorial provides a brief introduction to the
programming <span class="">language</span> by using one of its picture-drawing
libraries. This tutorial provides a brief introduction to the programming language. This tutorial provides a brief introduction to the programming language.</p>

<p class="em-and-a-half">This tutorial provides a brief introduction to the
programming <span class="smaller">language</span> by using one of its picture-drawing
libraries. This tutorial provides a brief introduction to the programming language. This tutorial provides a brief introduction to the programming language.</p>


<p class="caption">line-height: 1.5</p>

<p class="decimal">This tutorial provides a brief introduction to the
programming <span class="">language</span> by using one of its picture-drawing
libraries. This tutorial provides a brief introduction to the programming language. This tutorial provides a brief introduction to the programming language.</p>

<p class="decimal">This tutorial provides a brief introduction to the
programming <span class="smaller">language</span> by using one of its picture-drawing
libraries. This tutorial provides a brief introduction to the programming language. This tutorial provides a brief introduction to the programming language.</p>

The first two paragraphs have default line spacing. The second paragraph has one word that is smaller. But it doesn't affect the line spacing in that paragraph. Not that it should, but then —

The next two paragraphs have line-height: 150%. Again, the second paragraph has one word that's smaller. But this time, for reasons unclear, the smaller font creates extra space between the first two lines (at least in Safari, Chrome, Firefox and Explorer). This is the original problem in my CSS that I was trying to fix. (I speculate that it has something to do with the browser shrinking the word and then shifting it downward vertically to realign the baselines.)

The next two paragraphs have line-height: 1.5em. My understanding is that 1.5em is the same as 150%. And indeed, the behavior is the same: extra space between the first two lines of the second paragraph.

But here's where it gets weird: the next two paragraphs have line-height: 1.5 — no unit specified. This time, the extra-space problem disappears.

In sum, CSS seems to be giving consistent line-spacing results when the line heights of the parent & child are different (through inheritance of the unitless value) but inconsistent results when the line heights of the parent & child are the same.

Thus my questions:

  1. I know there's an intentional semantic difference in the CSS spec between 1.5 and 150% or its synonym, 1.5em. (Namely: a unitless value is passed to the child element and its line height is calculated using the child's font size, whereas a percentage or em value will cause a line height to be calculated for the parent, and then that calculated value is passed to the child.) But how does this account for the difference in behavior seen here? Where is the extra space coming from? If it's a consequence of some CSS positioning rule, then what is that rule?

  2. Or, if these examples should all render the same way, then which one is implemented incorrectly? (Note on Q2: The fact that the rendering quirk happens the same way across different browsers strongly suggests that none of them are implemented incorrectly, which will take you back to question (1).)

  3. In practical terms, is there a downside to switching to unitless measurements like 1.5 for line-height? (Answer: no)

Html Solutions


Solution 1 - Html

Based on clues in the proposed answers, I think the rendering behavior seen in these examples is counterintuitive, but correct, and mandated by the interaction of several rules in the spec, and the overall CSS box model.

  1. CSS calculates the leading L needed for a box by the formula line-height = L + AD, where AD is "the distance from the top to the bottom" of the font. Then "half the leading is added above A and the other half below D." So text that has font-size:16px and line-height:24px will have 4px of leading above and below. Text that font-size:8px and line-height:24px will have 8px of leading above and below.

  2. By default, however, "user agent must align the glyphs ... by their relevant baselines.". This starts to explain what's happening here. When line-height is specified by percentage or em, a computed value is inherited by the child (here, the smaller span). Meaning, the smaller span gets the same line-height as the parent block. But because of the L + AD formula, the text of that span has more leading on the top and bottom, and thus the baseline sits higher in its box. The browser pushes down the smaller span vertically to match the baselines.

  3. But then the browser has a new problem — how to deal with the line spacing in the enclosing block, which has been disrupted by this baseline-adjusting process. The spec resolves this too: the line-height of a block-level element "specifies the minimal height of line boxes within the element". Meaning, CSS makes no promise that you'll get your exact line-height, just that you'll get at least that amount. So the browser pushes the lines apart in the enclosing block so that the realigned child box will fit.

The reason this is counterinitutive is that it's the opposite of how most word processors and page-layout programs work. In these programs, a smaller stretch of text within a paragraph is aligned by its baseline (like CSS) but line height is enforced as a distance between baselines, not as a box surrounding the smaller text. But that's not a bug — CSS is designed around a box model. So in the end, we could say that this spacing behavior is a consequence of that model.

That still leaves us to explain the behavior in the example with the unitless line-height:

  1. First, note that when no line-height is specified, the browser will apply a unitless line-height by default. This is required by the spec: the initial value of line-height is normal, which is defined to have "the same meaning as <number>", and the spec recommends a value "between 1.0 and 1.2". And that's consistent with what we see in the examples above, where the paragraphs with line-height: 1.5 have the same behavior as the paragraphs with no line-height setting (i.e., they are impliedly getting line-height: normal)

  2. As others have pointed out, when the paragraph has line-height: 1.5, the calculated line-height of the paragraph is not inherited by the smaller span. Rather, the smaller span calculates its own line height based on its own font size. When the paragraph has line-height: 1.5; font-size: 14px, then its calculated line height is 14px * 1.5 = 21px. And if the smaller span only has the property font-size: 50%, then its font size is 14px * 50% = 7px, and its line height is 7px * 1.5 = 10.5px (which will generally be rounded to a whole pixel). But overall, the smaller box is half the size of the surrounding text.

  3. As before, the browser will vertically align the smaller span to the adjacent baseline. But this time, because the box around smaller is shorter than the surrounding text, this realignment doesn't have any side effects in the enclosing block. It already fits, so there's no need to spread the lines of the parent paragraph, as there was before.

Both cases represent a consistent implementation of the spec. That's good news, because it means we can predict the line-spacing behavior.

That brings us back to the original reason for this question. While I now understand that the CSS box model requires this behavior, as a practicing typographer, this is rarely the behavior I want. What I want is for the lines within a paragraph to have consistent & exact line spacing, even if some spans of text within that paragraph are smaller.

Unfortunately, it seems there's no way to directly enforce exact line spacing in CSS as one can in a word processor or page-layout program. Again, this is because of the CSS box model: it doesn't use a baseline-to-baseline line-spacing model, and line-height is specified to be a minimum measurement, not maximum.

But we can at least say that unitless line-height values produce the best approximation of exact line spacing in CSS. Fussy typographers like myself should feel comfortable using them, because unitless values are endorsed by the spec, and they produce consistent results across browsers. They are not a hack, nor are they deprecated.

The caveat is that they're still only an approximation. Unitless line-height values don't change the underlying CSS box model, nor the CSS box-positioning rules. So it's possible that in some edge cases, they won't have the intended result. But eternal vigilance is the price of good typography. Be careful out there.

Solution 2 - Html

EDIT

I have created a codepen to demonstrate the leading that is created using the different values. Hopefully this provides a better explanation.


I can answer your questions, but only theorize (at this point) about the extra spacing you're seeing (and provide a potential work-around).

Your Questions

> Is there an intentional semantic difference in CSS between 1.5 and 150% that accounts for the difference in behavior?

There is, actually!

The numeric factor (1.5 in this case) gets inherited and used to calculate the line-height of each descendant, relative to its font-size.

The percentage factor (150%) is used to calculate a line-height based on the parent element's font-size. The resulting, pre-calculated value is then inherited by its descendants.

> Or, if they're meant to be the same, then which one is implemented incorrectly?

They are intentionally different. (See the W3C spec)

> In practical terms, is there a downside to switching to raw decimals like 1.5 for line-height?

Usually, it is an advantage to use the decimal values, because the inherited line-height will adapt better. But there are cases when it won't suit your needs.


Extra Spacing Issue

I noticed if I set vertical-align on your small text to middle or bottom, the problem doesn't happen. But this isn't a very good solution.

I'm guessing this has to do with the fact that the smaller text is using the inherited calculated line-height, combined with it's placement on the line. Since the text is smaller and it sits lower, but has the same line-height as the surrounding line, the bottom half-leading will actually push further down, and the top half-leading won't reach as high as the surrounding text.

So let's say the base font-size is 16px, and the line-height is 24px. The leading will be 4px on either side ((24-16)/2). When the font-size is 50%, that is 8px, but the line-height stays 24px. So the leading becomes 8px on either side ((24-8)/2).

The baselines of the text will align, so all things being equal, you would expect the smaller text to extend 4px below the normal text. But since the text (and its corresponding content area) is smaller, the bottom half-leading starts farther back, which is why you only see a pixel or two change, and only at certain percentages (getting more as you get smaller - try it out)

I really should use images for this, but can't right now... maybe I can add some later.

I don't know if that helps at all, but I definitely have a better understanding of how line-height works in general now!

References: http://www.w3.org/wiki/CSS/Properties/line-height http://meyerweb.com/eric/thoughts/2006/02/08/unitless-line-heights/ http://www.maxdesign.com.au/articles/css-line-height/

Solution 3 - Html

It seems as the simple answer to your question would be: inheritance. line-height: 150% and line-height: 1.5em in your example inherits the computer value of its parent, whereas a numeric value does not.

In your example, the parent <p> has a line height of 21px, if you use percentage or ems to specify the line height of your nested element <span class="small"> it will be calculated based on the inherited computed value of 21px; 211.5 = 31.5, but if you use the numeric value for line height it will calculated based on the actual font size; (140.5)*1.5 = 10.5.

You have a perfect answer to your question here.

Solution 4 - Html

Matthew, try adding „line-height:1.5em;” to the „.smaller” class. Does it solve the problem for all the declarations?

I think that it will and if so, it has to be a matter of inheritance. The difference between 1.5em/150% and 1.5 (without any unit) is that the first two compute the value and set it in an absolute matter on its element, hence a child of that element without its own line-height declaration will inherit the computed value, and not the percentage.

Declaring the line-height without a unit, however, has a different outcome for that child element because what is actually inherited is that number (1,5 – in your case) which will be computed once again on the child element resulting a value related to it's own font-size and not its parent's.

You say that the spec defines all the version in the same way and so the results should be the same, but here's what is actually said:

<number>

The used value of the property is this number multiplied by the element's font size. Negative values are illegal. The computed value is the same as the specified value.

<percentage>

The computed value of the property is this percentage multiplied by the element's computed font size. Negative values are illegal.

This is the difference: what is actually computed. When you declare the line-heght as a percentage (or using ems) the computed value is the resulted numerica value, whereas, in the case of the unitless declaration, the computed value is the same with the declared value wich makes it possible for the child element to recompute.

In the meantime, I've done the test and I think I was right.

When you declare the line-height as a percentage (or using ems), the computed value is inherited by the span element. So the line-height of the span element will be 24px instead of 13.5px. But, since the font-size of the span element is in fact smaller, the below leading resulted will be bigger than its parent's. (24px-9px)/2=7.5px. So you'll get 7.5px leading below instead of (24-16)/2 (4px). You end up with an extra 7.5px-4px (3.5px) leading.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMatthew ButterickView Question on Stackoverflow
Solution 1 - HtmlMatthew ButterickView Answer on Stackoverflow
Solution 2 - HtmlTom PietrosantiView Answer on Stackoverflow
Solution 3 - HtmlunitarioView Answer on Stackoverflow
Solution 4 - HtmlSebastian TomitaView Answer on Stackoverflow