DEV Community

Leo
Leo

Posted on • Originally published at news.html.to

The lh unit sizes to the line, not the font

Nested a button icon in a paragraph and watched it float half a pixel above the baseline? Bumped up font-size for accessibility and had every ruled-line background lose its grid? These are line-height problems dressed up as font-size problems, and the CSS lh unit is the one that finally names them correctly.

In a new piece titled "You might need to know about the CSS lh unit," Ahmad Shadeed walks through a tour of use cases for lh on ishadeed.com. The value is small. The shift in mindset is bigger than it looks.

What lh actually resolves to

lh is a CSS length unit. It resolves to the computed value of the line-height property on the element it is used on. Not font-size. Not cap-height. The full line box. That distinction is the whole point.

If you write line-height: 1.5 on a font-size: 16px element, 1lh on that element is 24px. Push the font-size to 20px and 1lh becomes 30px. Change the line-height and it moves again. The unit tracks what a line of text actually occupies.

Sizing spacing to the line, not the font

Shadeed opens on the most familiar case:

p {
  margin-bottom: 1lh;
}
Enter fullscreen mode Exit fullscreen mode

One line of clear space between paragraphs, whatever font-size the reader ends up on. The rem you would normally reach for is anchored to the root font, not the paragraph's own line rhythm. lh is anchored to the paragraph itself.

Ruled-paper backgrounds that survive a resize

The pattern that finally sold me on it is the ruled-line background:

.content {
  background-image: linear-gradient(
    to bottom,
    transparent calc(100% - 1px),
    rgba(0, 0, 0, 0.15)
  );
  background-size: 100% 1lh;
}
Enter fullscreen mode Exit fullscreen mode

A one-pixel line sits at the bottom of a tile that is exactly one line-height tall. Repeat it, and the rules land under every line. Tune the type up or down and the rules follow. No hand-picked pixel numbers, no re-tuning per breakpoint.

The mechanism is background-repeat doing its usual work, with the tile height promoted from "some pixel I guessed" to a computed line-box.

Rounding a floated image to a full line

Here's the one that surprised me. Shadeed shows this pattern for a floated image whose height should snap to the nearest whole line, so wrapped text does not end on a half-line orphan:

.content img {
  float: left;
  height: calc-size(auto, round(up, size, 1lh));
}
Enter fullscreen mode Exit fullscreen mode

round() is the piece doing the snapping — round up from the intrinsic size in steps of 1lh. Notice what the syntax names: not "one line of type" as an abstract, but the exact line-height of this element. Change the type and the snap changes with it. Nothing in the CSS mentions pixels.

Fading out a fixed number of lines

For a preview list that stops at line five and fades on the way out:

.list {
  max-height: 5lh;
  mask-image: linear-gradient(
    to bottom,
    #000,
    #000 calc(100% - 1lh),
    transparent 100%
  );
}
Enter fullscreen mode Exit fullscreen mode

Five lines exactly, and the mask's fade covers the last line's worth of height. Bump the font, and both the clip and the fade stretch together. Try to reproduce that with rem and you will always be one media query short of matching.

Icons that live in the same line-box as the label

The button-icon case:

.button {
  --size: 0.8lh;
  svg {
    width: var(--size);
    height: var(--size);
  }
}
Enter fullscreen mode Exit fullscreen mode

0.8 of a line-box. The icon lives inside the same line-box as the label, and asking it to be a fraction of that line-box is closer to what you meant than any fixed em multiple. Change the button's line-height for a denser toolbar and the icon follows.

Two mental shifts worth keeping

First: the sizes that belong to text — margins between paragraphs, background ruling, inline icons, wrapped-image snapping — belong to line-height, not font-size. lh is the unit that finally names that.

Second: the rem-only habit was always a compromise. rem tracks the root font, which is convenient, and wrong at every place where two different type sizes coexist on the same page. lh tracks the actual line box you are in.

Try one substitution on your next component: swap a rem that lives near text for a lh. If the layout stops drifting when you scale type, you have found one of the places lh is quietly the right unit.

Top comments (0)