Gradually Revealing Underline With CSS

Apr 6, 2025

I'm going to share a little CSS magic that lets you create a really cool hover effect for links. I have to admit that even though I've written tons of CSS in my career, this one took me a bit of thinking to figure out before I could bring the idea to life.

The effect I was after was an underline that gradually reveals from left to right. While that in itself isn't hard to achieve, e.g., with a pseudo-element using position: absolute and animating the width with a transition, there's a catch that makes the pseudo-element approach fall short: if the text wraps onto multiple lines, only the final line is underlined.

Ideally, the underline should reveal line by line: starting at the top line's left edge, moving to the right edge, then picking up at the next line's left edge, and so on, until the end. Like this, try hovering over it:

This is a long text which will be underlined on hover by gradually revealing the underline one line at a time. Ain't that the coolest effect you've seen?

The trick is to take advantage of display: inline and background-image. Let's build it step by step, starting with a <p> element that contains enough text to wrap across multiple lines.

Step 1: Add a Background Image

First, we need to set the background-image to the desired underline. To make it a solid color, we can use linear-gradient with the same color twice, e.g., linear-gradient(currentColor, currentColor) to match the text's color. Of course, you could use any other color, or even a gradient or image via url(). Here's that same block of text with the background added:

This is a long text which will be underlined on hover by gradually revealing the underline one line at a time. Ain't that the coolest effect you've seen?

Step 2: Make the Text Inline

Now we have a background, but it fills the whole block; it's time to add the secret sauce and set display: inline on the <p> element. This makes the text behave like a single flowing chunk that breaks into multiple lines due to its length, unlike a block element which wraps everything inside a rectangle.

You'll notice that with display: inline; the outlines of the each individual lines are seen, instead of just a big rectangle. Just a heads up: make sure the parent element isn't using flex, inline-flex, grid, or inline-grid; this tripped me during implementation.

This is a long text which will be underlined on hover by gradually revealing the underline one line at a time. Ain't that the coolest effect you've seen?

Step 3: Trim the Background

Next, we want the background to be full-width but limited in height. You can do this with background-size: 100% var(--underline-thickness);, where --underline-thickness is a variable containing the desired underline thickness — we can replace it with a magic number too if we don't need to make the code modular, e.g., background-size: 100% 1px; for a full-width but pixel-thick line.

Also add background-repeat: no-repeat so the line doesn't get drawn over and over again, and use background-position: 0 100% to place the underline at the bottom.

At this point, the underline sits beneath the text, right where we want it.

This is a long text which will be underlined on hover by gradually revealing the underline one line at a time. Ain't that the coolest effect you've seen?

Step 4: Animate the Reveal

Finally, let's make the underline gradually reveal on hover. To do that, we'll initially set the background-size to background-size: 0 var(--underline-thickness);, and change it to background-size: 100% var(--underline-thickness); on hover. Add a transition like transition: background-size 700ms ease-in-out; for a smooth reveal, though obviously the options here are limitless. Here's the finished block again:

This is a long text which will be underlined on hover by gradually revealing the underline one line at a time. Ain't that the coolest effect you've seen?

To put it all together, here's the code:

<style>
  --underline-thickness: 1px;

  .underline-reveal {
    background-image: linear-gradient(currentColor, currentColor);
    background-size: 0% var(--underline-thickness);
    background-repeat: no-repeat;
    background-position: 0 100%;
    display: inline;
    transition: background-size 700ms ease-in-out;
  }

  .underline-reveal:hover {
    background-size: 100% var(--underline-thickness);
  }
</style>
<p class="underline-reveal">
  This is a long text which will be underlined on hover
  by gradually revealing the underline one row at a time.
  Ain't that the coolest effect you've seen?
</p>