Fancy link underlines

Author Jesse Breneman Published on January 19, 2021

Links are often styled with underlines, but text-decoration is pretty boring and inflexible. We're going to examine using background images to create a bunch of different underlines. This is by far the best method for flexible underlines that I know of and has very few downsides and some great upsides.

Demo

Basic method

The TL;DR for this is that we're going to use a linear-gradient as a background image and then constrain where it shows with a combination of background-size and background-position.

css
.link {
    display: inline;
    text-decoration: none;
    background-image: linear-gradient(120deg, #0ebeff, #0ebeff);
    background-size: 100% 2px;
    background-position: 0 95%;
    background-repeat: no-repeat;
}

.link:hover {
    background-image: linear-gradient(120deg, #ff0eff, #ff0eff);
}

Let's break that down by property so we understand what's going on here.

  • display: inline: Since this relies on a background image, we need the background to be applied to just the text itself. This also gives us an underline that wraps to the next line nicely, which most other methods will not give you.
  • text-decoration: This is set to none so the default underline doesn't interfere with the underline we're about to add.
  • background-image: A linear-gradient with a single color will give you a solid block of color.
  • background-size: Using two-value syntax like this, the first value will be the width of the background and the second will be the height of the background.
  • background-position: Two-value syntax here sets the x & y position of the background. In this case, it sets the x to 0, which is all the way to the left, and the y to 95%, which is 95% from the top. Usually I set the second value to something between 90% and 100%.
  • background-repeat: The background we've defined will not fill the entire container, so the browser will "helpfully" fill it in for us. This tells the browser that we just want to show our background once.

Upsides

  • More control over the underline compared to other methods.
    • text-decoration is really limited, you can't control the height and controlling the color via text-decoration-color is not well supported yet. You can't control where the underline is relative to the text.
    • Using a border-bottom you can control the height and color, but not how it's positioned relative to the text, and usually feels like it's positioned too far below the text.
    • Using an absolutely positioned pseudo-element gives you tons of control, but doesn't give you the ability to do multi-line underlines.
  • Can set an underline to a solid color, gradient, or any image.
  • Capable of a bunch of interesting transitions.

Downsides

  • There's no good way of emulating text-decoration-skip-ink.
  • Your element needs to be inline. Generally this is not really an issue as links are almost always inline anyway, but I'm including it anyway because it is a restriction.
  • background-image can't be transitioned. This means you have to get creative about how you do your transitions, because background-position and background-size_ are_ able to transition.

Variations

Solid color is great, but there's a lot more we can do with this. Let's look at some different fills we can do and then move on to several different hover animations.

Basic color fills

This is the simplest version of this, just a gradient with one color.

css
.link {
    background-image: linear-gradient(120deg, #a6e22e, #a6e22e);
}

You can play with the height of the underline as well. Generally I stick to 1px or 2px.

css
.link {
    background-image: linear-gradient(120deg, #fd971f, #fd971f);
    background-size: 100% 1px;
}

Gradient fills

Getting a little more fancy, you can make this an actual gradient.

css
.link {
    background-image: linear-gradient(120deg, #66d9ef, #ae81ff);
}

You could also change the angle to be top to bottom as well for a different effect.

css
.link {
    background-image: linear-gradient(0deg, #ae81ff, #66d9ef);
    background-size: 100% 6px;
}

The sky's the limit here, a radial-gradient or repeating gradients could lead to some really interesting results.

Embedded images

Using an svg that's encoded for CSS, you can get some really cool effects. The most common use of this I've seen around the web is a sine wave effect. Transition effects are limited due to this needing to use a repeating background, but you can do a lot of really neat things with this. A base64 encoded bitmap image would work as well, but an svg is going to scale more easily.

css
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 19.3 6'%3E%3Cpath d='M-5.5 3.8C-4.2 4.8-2.8 6 0 6s4.2-1.2 5.5-2.2C6.6 2.8 7.6 2 9.7 2s3 .8 4.2 1.8c1.3 1 2.7 2.2 5.5 2.2s4.2-1.2 5.5-2.2l-1.4-.9c-1.1 1-2.1 1.8-4.2 1.8s-3-.8-4.2-1.8C13.9 1.9 12.4.7 9.7.7 6.9.7 5.5 1.9 4.2 2.9 3 3.9 2.1 4.7 0 4.7s-3-.8-4.2-1.8l-1.3.9z' fill='black'/%3E%3C/svg%3E");
background-size: 11px 4px;
background-position: 0 95%;
background-repeat: repeat-x;

Animation variations

Basic color changes

These don't have a smooth transition due to them relying on a background-image, but the simplest change you can make is to simply swap the colors out for new colors.

css
.link:hover {
    background-image: linear-gradient(120deg, #ff0eff, #ff0eff);
}
css
.link:hover {
    background-image: linear-gradient(120deg, #ff0eff, #0ebeff);
}

Repositioning the line

Another basic animation you can do is to move where the line is by changing the background-position. This will be a smooth transition, but can feel a little weird depending on how far you move it and what easing you use.

css
.link:hover {
    background-position: 0 100%;
}
css
.link:hover {
    // This is 53% instead of 50% to make it look vertically centered
    background-position: 0 53%;
}
css
.link:hover {
    background-position: 0 0;
}

Vertical fill

Instead of moving the line around, we can actually morph it into a different shape by changing the background-size to be larger.

css
.link:hover {
    background-size: 100% 100%;
}

Horizontal movement

Finally, you can move the line horizontally as well. One way of doing thing is to animate the background-size to 0, which gives you a sort of wipe effect on the line.

css
.link:hover {
    background-size: 0% 2px;
}

Another horizontal movement you can do is to add an infinitely moving animation to the hover state. You can do a lot of crazy stuff with this since it uses a keyframe animation, but here's the simple version of this.

For the gradient, we're sizing it to be larger than the element and creating a gradient that will seamlessly animate when we move it horizontally.

css
@keyframes horizontal-movement {
    from {
        background-position: 0 90%;
    }
    to {
        background-position: 100% 90%;
    }
}

.link {
    --color-1: #0ebeff;
    --color-2: #ff0eff;
    background-image: linear-gradient(
        120deg,
        var(--color-1),
        var(--color-2),
        var(--color-1),
        var(--color-2)
    );
    background-size: 300% 2px;
    background-position: 0 90%;
}

.link:hover {
    animation: 1s horizontal-movement infinite linear;
}

For the image version of this, since we're using background-repeat we can pretty much just move this from 0% to 100%.

css
@keyframes horizontal-movement {
    from {
        background-position: 0 90%;
    }
    to {
        background-position: 100% 90%;
    }
}

.link {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 19.3 6'%3E%3Cpath d='M-5.5 3.8C-4.2 4.8-2.8 6 0 6s4.2-1.2 5.5-2.2C6.6 2.8 7.6 2 9.7 2s3 .8 4.2 1.8c1.3 1 2.7 2.2 5.5 2.2s4.2-1.2 5.5-2.2l-1.4-.9c-1.1 1-2.1 1.8-4.2 1.8s-3-.8-4.2-1.8C13.9 1.9 12.4.7 9.7.7 6.9.7 5.5 1.9 4.2 2.9 3 3.9 2.1 4.7 0 4.7s-3-.8-4.2-1.8l-1.3.9z' fill='var(--color-1)'/%3E%3C/svg%3E");
    background-size: 11px 4px;
    background-position: 0 90%;
    background-repeat: repeat-x;
}

.link:hover {
    animation: 5s horizontal-movement infinite linear;
}

If you're doing any animations like this where there's a lot of movement be sure to use a prefers-reduced-motion media query to tone it down for users that have issues with motion.

css
@media (prefers-reduced-motion) {
    .link:hover {
        // Use a less motion-y animation or just remove it entirely
        animation-name: none;
    }
}

And that's it! This method can be used in a lot of really clever ways, what I've shown here is really only scratching the surface.

About the author