Fun with line-height!

Avatar of Chris Coyier
Chris Coyier on

📣 Freelancers, Developers, and Part-Time Agency Owners: Kickstart Your Own Digital Agency with UACADEMY Launch by UGURUS 📣

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.

.text {
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}

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.

.text {
  background-image: linear-gradient(
    to bottom,
    #9588DD,
    #9588DD 22px,
    #DD88C8 22px,
    #DD88C8 44px,
    #D3DD88 44px,
    #D3DD88 66px,
    #88B0DD 66px,
    #88B0DD);
}

Combining those two tricks:

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.

$lh: 1.4em;

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

@supports (-webkit-background-clip: text) {
  p {
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-image: linear-gradient(
      to bottom,
      #9588DD,
      #9588DD $lh,
      #DD88C8 $lh,
      #DD88C8 $lh*2,
      #D3DD88 $lh*2,
      #D3DD88 $lh*3,
      #88B0DD $lh*3,
      #88B0DD);
  }
}

Using this behavior at the top of the element is easiest. Here’s an example where the first few lines are altered for emphasis.

.text {
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-image: linear-gradient(
    to bottom,
    rgba(white, 0.8),
    rgba(white, 0.8) $lh,
    rgba(white, 0.6) $lh,
    rgba(white, 0.6) $lh*2,
    rgba(white, 0.4) $lh*2,
    rgba(white, 0.4) $lh*3,
    rgba(white, 0.2) $lh*3,
    rgba(white, 0.2));
}

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()!

.text {
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-image: linear-gradient(
    to bottom,
    rgba(white, 0.8),
    rgba(white, 0.8) calc(100% - 66px),
    rgba(white, 0.6) calc(100% - 66px),
    rgba(white, 0.6) calc(100% - 44px),
    rgba(white, 0.4) calc(100% - 44px),
    rgba(white, 0.4) calc(100% - 22px),
    rgba(white, 0.2) calc(100% - 22px),
    rgba(white, 0.2));
  background-position: bottom center;
}

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).

.parent {
  padding: $lh*2;
  background: #082838;
  background-image: repeating-linear-gradient(
    to bottom,
    rgba(white, 0)   0,
    rgba(white, 0)   $lh/1em*16px-1,
    rgba(white, 0.1) $lh/1em*16px-1,
    rgba(white, 0.1) $lh/1em*16px
  );
}

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.

.text
  background-image: url(image.svg);
  background-size: $lh $lh;
  background-repeat: repeat-y;
  padding-left: $lh*2;
}

Demos

See the Pen One line of Text Dif Color by Chris Coyier (@chriscoyier) on CodePen.