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
.
.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 tonone
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 viatext-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, becausebackground-position
andbackground-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.
.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.
.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.
.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.
.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.
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.
.link:hover {
background-image: linear-gradient(120deg, #ff0eff, #ff0eff);
}
.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.
.link:hover {
background-position: 0 100%;
}
.link:hover {
// This is 53% instead of 50% to make it look vertically centered
background-position: 0 53%;
}
.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.
.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.
.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.
@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%.
@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.
@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.