Fun with line-height!

The line-height property in CSS controls the space between lines of text. It is often set in a unitless value (e.g. line-height: 1.4;) so that it is proportional to the font-size. It's a vital property for typographic control. Too low and lines are awkwardly squished together; too high and lines are awkwardly far apart. Both inhibit readability. But you probably already know that.

In this article we'll focus on some trickery. If you know (or can figure out) the exact value of line-height, you can do some neat stuff!

Style each line of text a different color

There is no ::nth-line(), unfortunately. We can't really even use <span>s reliably, as there are tons of different things that can cause text to break at different points.

There is a way, albeit non-standard, to use the background of an element as the background of text.

There is another trick where you can use a linear-gradient() with color-stops such that the color doesn't fade into another, it just abruptly ends and another starts. Let's say we know that the line-height is 22px, we can make the gradient breaks right at that.

In a browser that doesn't support the text background clipping, like Firefox, you would get solid bars of color behind the text. Maybe that's cool and you like it. Maybe you'd rather just fall back to a solid color text. In that case, you can use @supports to only apply it if supported anyway.

Also, since you're using the value of line-height over and over, might be nice to variablize it. I'll use SCSS here, but this would be kinda neat to do with real CSS variables someday so you could change it even after rendering and watch it all keep working.

It gets more difficult if we're trying to target the last few lines of an arbitrary amount of text. In that case, we'll need the first color band to go from the top to all-the-way-down-minus-a-few-lines. Fortunately we can do that with calc()!

There are other ways to do this kind of thing as well, like overlaying a pseudo element gradient (with pointer-events: none; so it's not annoying).

Lines Between Text

Using a similar technique to the solid-color-stops technique we used above, we can create a 1px line gradient that repeats exactly at the known line-height. The easiest way is to use repeating-linear-gradient(), as well as make sure all other elements play nicely (like padding that is also based on line-height).

In order to get the 1px line, we need to know what the line-height is in pixels, then subtract one. The goal is that the gradient repeats at exactly the known line-height, so the last pixel in that space can be the line. Because we've left the body font-size at 1em, that's 16px. And since the line-height is set in ems, we can divide by 1em removing the unit, then multiply by 16px and subtract one when needed.

Position images one-per-line

Another thing you can do if you know the exact line-height is to make background-size match it, at least on the vertical axis. Then you can make it repeat vertically and it will line up one-image-per-line.

Comments

On targeting the last few lines: Why mess with calc() instead of just changing the direction of the gradient to “to top” instead of “to bottom”?

On the lines example: This is really messy, without needing to be so. You can just use plain linear gradients with color stops at 1px, then use background-size in ems. NEVER EVER specify line heights in absolute lengths. It’s bound to break horribly when some careless developer changes the font size without bothering to change the line-height as well.

If anyone is wondering why the 1. example (multi-color) doesn’t work in Safari, but the 2. example (fade-to-gray) does, it’s because the CSS of 1. example relies on @supports (which Safari does not support).

The gradient at the bottom is not just how to reverse the gradient, it’s solving a different problem, namely what it states in the article: “trying to target the last few lines of an arbitrary amount of text.”

Neat tricks, and they feel ‘good’ — as in, not abusing CSS (although it would be great to have an ::nth-line() to make our intent more explicit). One thing I should point out, though, is that with that ‘lined paper’ effect, Blink has a nasty bug that makes the gradients render at the wrong scale. There are multiple bug reports about it, but no traction as far as I can see. If people could vote on a few of them, that might spur some action.

Some of these fail in pretty weird ways if the zoom level is changed on the browser for any reason (which I actually know a lot of people do, as it’s easier to set the zoom on a browser than get their font sizes fixed correctly system-wide on high-res screens)

Neat and interesting effects. Fade out bottom effect is amazing, and I did not think that it was possible via CSS. Before that we had to use PNG half transparent images and absolute positions with Z-Index to create that effect over text box. Above in examples, it’s a very good practice to create that effect. Thanks for sharing.

👋

CSS-Tricks* is created, written by, and maintained by Chris Coyier and a team of swell people. The tech stack for this site is fairly boring. That's a good thing! I've used WordPress since day one all the way up to v17, a decision I'm very happy with. I also leverage Jetpack for extra functionality and Local for local development.