Creating a Sliding Text Animation with Tailwind CSS
In this tutorial, we’ll learn how to create a sliding text animation using only Tailwind CSS. The animation will display a word sliding from the bottom to the top, replacing the previous word. This effect is especially helpful when you want to alternate multiple words in a sentence (similar to a carousel) and keep the overall density of text compact. You’ve probably seen this effect used a lot for headlines, but the use is not limited to them, and you can try it out in any title or even in paragraphs.
The best part of this tutorial? We will achieve all this using only Tailwind CSS—no need to write a single line of JavaScript!
Are you ready?
Let’s get started!
Creating a new document with Tailwind CDN
For this experiment, we’ll utilize the Tailwind CSS CDN. While it might not be the best approach for real projects, it’s perfect for a tutorial like this. The CDN lets us use Tailwind directly in the browser, without any installation or configuration hassles. Additionally, we will be able to customize Tailwind’s configuration directly in a script
tag.
Let’s start by creating a new HTML file and setting up the structure to host our carousel of words:
<!DOCTYPE html>
<html lang="en"> <head> <meta charset="utf-8"> <title>Rotating Words Animation</title> <meta name="viewport" content="width=device-width,initial-scale=1"> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet"> <script src="https://cdn.tailwindcss.com"></script> <script> tailwind.config = { theme: { extend: { fontFamily: { inter: ['Inter', 'sans-serif'], }, }, }, }; </script>
</head> <body class="relative font-inter antialiased"> <main class="relative min-h-screen flex flex-col justify-center bg-slate-900 overflow-hidden"> <div class="w-full max-w-6xl mx-auto px-4 md:px-6 py-24"> <div class="text-center"> <!-- Rotating words animation --> </div> </div> </main>
</body> </html>
Now, let’s replace the comment with the HTML code for the headline:
<div class="font-extrabold text-3xl md:text-4xl [text-wrap:balance] bg-clip-text text-transparent bg-gradient-to-r from-slate-200/60 to-50% to-slate-200">Trusted by the most innovative minds in <span class="text-indigo-500 inline-flex flex-col"> <ul class="block text-left leading-tight [&_li]:block"> <li>Finance</li> <li>Tech</li> <li>AI</li> <li>Crypto</li> <li>eCommerce</li> </ul>
</span></div>
We’ve created a line of text accompanied by an unordered list of words that we want to rotate. Notice how the first word “Finance” lines up perfectly with the white text, while the others wait on separate lines.
But, before we dive into showing one word at a time from the list, let’s take a moment to define the animation itself. Once the animation is all set up, we can move on to taking care of how we hide the other words in the list.
Animating the word list block
OK. We have a list of words, and we want them to scroll up at regular intervals. To achieve this, we’ll use the CSS animation property. But before we do that, we’ll duplicate the first element and append it at the end. This ensures that the scrolling animation occurs seamlessly, without any disruptive interruptions. Let’s modify the list like this:
<div class="font-extrabold text-3xl md:text-4xl [text-wrap:balance] bg-clip-text text-transparent bg-gradient-to-r from-slate-200/60 to-50% to-slate-200">Trusted by the most innovative minds in <span class="text-indigo-500 inline-flex flex-col"> <ul class="block text-left leading-tight [&_li]:block"> <li>Finance</li> <li>Tech</li> <li>AI</li> <li>Crypto</li> <li>eCommerce</li> <li aria-hidden="true">Finance</li> </ul>
</span></div>
Since the duplicated element is only needed for the animation, we have used the attribute aria-hidden="true"
to make it accessible but not visible to screen readers.
Perfect! With a list of 6 elements, we need an animation with 6 steps. Let’s define the translation values for each step. To calculate them, we’ll use the formula 100% / number of elements
. In our case, 100% / 6 = 16.6666666667%
. So, the translation values will be:
- 0%
- 16.66%
- 33.33%
- 50%
- 66.66%
- 83.33%
Now we need to identify the appropriate keyframe-selectors
. These are crucial points of the animation to which we’ll assign the translation values we calculated earlier. Through some experimentation, we found that for a good result, each word should remain visible for 4/5 of the time and translate for 1/5 of the time.
So, to calculate the keyframe-selectors
, follow these steps:
- Divide 100% by 5 (the actual number of elements in the list):
100% / 5 = 20%
- Calculate the transition time, defined as 1/5:
20% / 5 = 4%
- Calculate the dwell time, defined as 4/5:
20% / 5 * 4 = 16%
Now, we have all the elements to define our animation. Let’s define our animation with @keyframes
:
'0%, 16%': { transform: 'translateY(0%)',
},
'20%, 36%': { transform: 'translateY(-16.66%)',
},
'40%, 56%': { transform: 'translateY(-33.33%)',
},
'60%, 76%': { transform: 'translateY(-50%)',
},
'80%, 96%': { transform: 'translateY(-66.66%)',
},
'100%': { transform: 'translateY(-83.33%)',
},
The whole animation will last for 12.5
seconds (2.5
seconds per word), and here’s how the transitions will work:
- From 0 to 2 seconds, the word Finance is visible
- From 2 to 2.5 seconds, the transition from Finance to Tech takes place
- From 2.5 to 4.5 seconds, the word Tech is visible
- From 4.5 to 5 seconds, the transition from Tech to AI takes place
- From 5 to 7 seconds, the word AI is visible
- From 7 to 7.5 seconds, the transition from AI to Crypto takes place
- From 7.5 to 9.5 seconds, the word Crypto is visible
- From 9.5 to 10 seconds, the transition from Crypto to eCommerce takes place
- From 10 to 12 seconds, the word eCommerce is visible
- From 12 to 12.5 seconds, the transition from eCommerce to Finance (the one added to the end of the list) takes place
After the last transition, the last word in the list is replaced by the first word (without any transition), and the animation starts again from the beginning, creating a sense of continuity!
Debugging the animation
Now that we’ve defined all the animation values, it’s time to incorporate them into the tailwind.config
object:
tailwind.config = { theme: { extend: { fontFamily: { inter: ['Inter', 'sans-serif'], }, animation: { 'text-slide': 'text-slide 12.5s cubic-bezier(0.83, 0, 0.17, 1) infinite', }, keyframes: { 'text-slide': { '0%, 16%': { transform: 'translateY(0%)', }, '20%, 36%': { transform: 'translateY(-16.66%)', }, '40%, 56%': { transform: 'translateY(-33.33%)', }, '60%, 76%': { transform: 'translateY(-50%)', }, '80%, 96%': { transform: 'translateY(-66.66%)', }, '100%': { transform: 'translateY(-83.33%)', }, }, }, }, },
};
In addition to setting the duration to 12.5
seconds, we’ve also defined a cubic-bezier
curve to make the animation smoother and more natural.
Now, to see it in action, all we need to do is add the class animate-text-slide
to our ul
element:
<div class="font-extrabold text-3xl md:text-4xl [text-wrap:balance] bg-clip-text text-transparent bg-gradient-to-r from-slate-200/60 to-50% to-slate-200">Trusted by the most innovative minds in <span class="text-indigo-500 inline-flex flex-col"> <ul class="block animate-text-slide text-left leading-tight [&_li]:block"> <li>Finance</li> <li>Tech</li> <li>AI</li> <li>Crypto</li> <li>eCommerce</li> <li aria-hidden="true">Finance</li> </ul>
</span></div>
Displaying one word at a time
Here we are at the last step, where we will create a kind of mask to display only one word at a time, namely the one currently aligned with the white text.
To do this, we need to set a fixed height to the element containing the unordered list, equal to the line-height of the elements in the list. Then, we need to set the overflow of the element to hidden
to hide the words that are not aligned with the white text.
So, Let’s add these classes to the element:
-
h-[calc(theme(fontSize.3xl)*theme(lineHeight.tight))]
sets the correct height on smaller screens -
md:h-[calc(theme(fontSize.4xl)*theme(lineHeight.tight))]
sets the correct height from medium screens onwards -
overflow-hidden
hides everything that exceeds
Let’s unveil the final result:
<div class="font-extrabold text-3xl md:text-4xl [text-wrap:balance] bg-clip-text text-transparent bg-gradient-to-r from-slate-200/60 to-50% to-slate-200">Trusted by the most innovative minds in <span class="text-indigo-500 inline-flex flex-col h-[calc(theme(fontSize.3xl)*theme(lineHeight.tight))] md:h-[calc(theme(fontSize.4xl)*theme(lineHeight.tight))] overflow-hidden"> <ul class="block animate-text-slide-5 text-left leading-tight [&_li]:block"> <li>Finance</li> <li>Tech</li> <li>AI</li> <li>Crypto</li> <li>eCommerce</li> <li aria-hidden="true">Finance</li> </ul>
</span></div>
The animation works like a charm, but what if you want to add or remove words from the list? ?
Changing the number of words in the carousel
Now, we have good news and bad news! ?
Let’s start with the bad, just to get it out of the way. If you ever need to adjust the animation for a different number of words, you’ll have to recalculate all the animation values.
The good news is that we’ve done it for you, defining animations for word counts ranging from 2 to 8 words! You can find them all in the tailwind.config.js
object:
animation: { 'text-slide-2': 'text-slide-2 5s cubic-bezier(0.83, 0, 0.17, 1) infinite', 'text-slide-3': 'text-slide-3 7.5s cubic-bezier(0.83, 0, 0.17, 1) infinite', 'text-slide-4': 'text-slide-4 10s cubic-bezier(0.83, 0, 0.17, 1) infinite', 'text-slide-5': 'text-slide-5 12.5s cubic-bezier(0.83, 0, 0.17, 1) infinite', 'text-slide-6': 'text-slide-6 15s cubic-bezier(0.83, 0, 0.17, 1) infinite', 'text-slide-7': 'text-slide-7 17.5s cubic-bezier(0.83, 0, 0.17, 1) infinite', 'text-slide-8': 'text-slide-8 20s cubic-bezier(0.83, 0, 0.17, 1) infinite',
},
keyframes: { 'text-slide-2': { '0%, 40%': { transform: 'translateY(0%)', }, '50%, 90%': { transform: 'translateY(-33.33%)', }, '100%': { transform: 'translateY(-66.66%)', }, }, 'text-slide-3': { '0%, 26.66%': { transform: 'translateY(0%)', }, '33.33%, 60%': { transform: 'translateY(-25%)', }, '66.66%, 93.33%': { transform: 'translateY(-50%)', }, '100%': { transform: 'translateY(-75%)', }, }, 'text-slide-4': { '0%, 20%': { transform: 'translateY(0%)', }, '25%, 45%': { transform: 'translateY(-20%)', }, '50%, 70%': { transform: 'translateY(-40%)', }, '75%, 95%': { transform: 'translateY(-60%)', }, '100%': { transform: 'translateY(-80%)', }, }, 'text-slide-5': { '0%, 16%': { transform: 'translateY(0%)', }, '20%, 36%': { transform: 'translateY(-16.66%)', }, '40%, 56%': { transform: 'translateY(-33.33%)', }, '60%, 76%': { transform: 'translateY(-50%)', }, '80%, 96%': { transform: 'translateY(-66.66%)', }, '100%': { transform: 'translateY(-83.33%)', }, }, 'text-slide-6': { '0%, 13.33%': { transform: 'translateY(0%)', }, '16.66%, 30%': { transform: 'translateY(-14.28%)', }, '33.33%, 46.66%': { transform: 'translateY(-28.57%)', }, '50%, 63.33%': { transform: 'translateY(-42.85%)', }, '66.66%, 80%': { transform: 'translateY(-57.14%)', }, '83.33%, 96.66%': { transform: 'translateY(-71.42%)', }, '100%': { transform: 'translateY(-85.71%)', }, }, 'text-slide-7': { '0%, 11.43%': { transform: 'translateY(0%)', }, '14.28%, 25.71%': { transform: 'translateY(-12.5%)', }, '28.57%, 40%': { transform: 'translateY(-25%)', }, '42.85%, 54.28%': { transform: 'translateY(-37.5%)', }, '57.14%, 68.57%': { transform: 'translateY(-50%)', }, '71.42%, 82.85%': { transform: 'translateY(-62.5%)', }, '85.71%, 97.14%': { transform: 'translateY(-75%)', }, '100%': { transform: 'translateY(-87.5%)', }, }, 'text-slide-8': { '0%, 10%': { transform: 'translateY(0%)', }, '12.5%, 22.5%': { transform: 'translateY(-11.11%)', }, '25%, 35%': { transform: 'translateY(-22.22%)', }, '37.5%, 47.5%': { transform: 'translateY(-33.33%)', }, '50%, 60%': { transform: 'translateY(-44.44%)', }, '62.5%, 72.5%': { transform: 'translateY(-55.55%)', }, '75%, 85%': { transform: 'translateY(-66.66%)', }, '87.5%, 97.5%': { transform: 'translateY(-77.77%)', }, '100%': { transform: 'translateY(-88.88%)', }, }
},
So, if your list contains only two words, you just need to use the class animation-text-slide-2
on your <ul>
element. For example:
<div class="font-extrabold text-3xl md:text-4xl [text-wrap:balance] bg-clip-text text-transparent bg-gradient-to-r from-slate-200/60 to-50% to-slate-200">Trusted by the most innovative minds in <span class="text-indigo-500 inline-flex flex-col h-[calc(theme(fontSize.3xl)*theme(lineHeight.tight))] md:h-[calc(theme(fontSize.4xl)*theme(lineHeight.tight))] overflow-hidden"> <ul class="block animate-text-slide-5 text-left leading-tight [&_li]:block"> <li>Finance</li> <li>Tech</li> <li aria-hidden="true">Finance</li> </ul>
</span></div>
Conclusions
We hope this tutorial will give your headlines/sentences a bit of character, and if you’re interested in learning more about similar tips and tricks, check out our section of Tailwind CSS tutorials.