July 7, 2025
Smooth animations with text-shadow
Now it's time for some posts focusing on the more visual part of frontend development after the Lighthouse series with focus on some more technical parts optimizing your website.
I love playing around with smooth animations for the solutions I'm working on even though sometimes there's not enough time for it. I don't just do animations to do animations - they need to have a purpose. That purpose could be to help the user understand what's happening on your site and understanding the flow of the site. That could be animations that occur when the user interacts with parts of your website like submitting forms, clicking on elements, submitting forms etc. But the purpose could also be the purpose for your company and brand. Animations can really help giving the correct look, feel and understanding of your brand.
A lot of developers and designers use animations on texts on their sites. It could be on headlines, buttons etc. I've created a tiny codepen with a smooth text animation on a button:
Let's brake it down!
Sometimes I see developers (including myself just a few years ago) duplicating the text or letters to do animations like this. But there's really no need to do that. You can easily do this animation with the text-shadow instead. Using the text-shadow you don't have to duplicate the content - and Google (and other search-engines) must be way more happy when the content is not duplicated.
Let's have a look at the markup for my button:
<a class="btn">
<span class="btn__text">
<span class="btn__letter">H</span>
<span class="btn__letter">o</span>
<span class="btn__letter">v</span>
<span class="btn__letter">e</span>
<span class="btn__letter">r</span>
<span class="btn__letter">m</span>
<span class="btn__letter">e</span>
</span>
</a>
It's some quite simple markup with just a link with the btn class, an element inside that wraps the entire text and each letter inside an element of it's own.
Let's have a look at the CSS as well:
.btn {
background-color: $color-green;
padding: 24px 32px;
position: relative;
font-size: 16px;
letter-spacing: 0.2em;
line-height: 1.2em;
cursor: pointer;
&__text {
display: inline-flex;
overflow: hidden;
position: relative;
z-index: 2;
}
&__letter {
display: block;
position: relative;
text-shadow: 0 1.25em;
transition: transform 0.8s cubic-bezier(0.625, 0.05, 0, 1);
@for $i from 1 through 7 {
&:nth-child(#{$i}) {
transition-delay: #{($i * 0.01)}s;
}
}
}
&:hover &__letter {
transform: translateY(-1.25em) translateZ(0);
}
}
As you can see, the CSS is quite simple as well. The most important parts is the element wrapping the letters and the animations.
Text wrap
The text wrap element is important as we need this to avoid the text-shadow being visible inside the button. We've got padding on our button and just putting the letters inside it would make the text-shadow visible as long as it's inside the button - and we don't want the text-shadow completely outside the button as it would make the animation too much. Instead we add the text wrapping element and add overflow: hidden to it, to avoid the text-shadow being visible.
Animations
The other important thing is, off course, the animation itself. The text-shadow I've added reflects the text 1:1 on the Y axis - just below the original text - and that's why we need to transform each letter on the Y axis on hover - pushing text text up to a position where the text-shadow will be in the exact same spot as the original text. This is way we add translateY(-1.25em) on hover. The 1.25em is the same as the distance of the shadow. To make each letter appear slightly more delayed than the letter before I've added a loop setting transition-delay on each letter using the :nth-child() selector.
It's as simple as that!
If you don't want to add the animation to each letter, you could just wrap all letters in on parent and adding the animation to that.
Conclusion
So what we did to make a smooth animation like this was actually pretty simple:
We added simple markup with an element wrapping all the single letters, each surrounded by an element of it's own
We add a text-shadow to each letter, hiding it with overflow hidden
We translated each letter, with a slight higher delay of each letter in the button, making the transition super-smooth.
That's about it. You don't necessarily need an animation library to do simple smooth animations like this.
If you’ve got any questions regarding this article — or others — please don’t hesitate to reach out at [email protected]. You’re also welcome to reach out if you have further tips & tricks you see missing in my articles. I’ll update them and credit you.