Creating a Sliding Text Animation with Tailwind CSS
Tags: tailwindcss, tutorial, webdev, html
Published: 2023-08-15 09:38:06

####**[Live Demo](https://cruip-tutorials.vercel.app/rotating-words/) / [Download](https://github.com/cruip/cruip-tutorials/blob/main/rotating-words/index.html)**

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:

```html
<!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>
```

Creating the headline
---------------------

Now, let’s replace the comment with the HTML code for the headline:

```html
<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.

![The list of items](https://cruip.com/wp-content/uploads/2023/07/create-the-list-768x264.png)

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:

```html
<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.

![Duplicate the first list item](https://cruip.com/wp-content/uploads/2023/07/create-the-list-02-768x293.png)

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`:

```css
'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:

```javascript
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:

```html
<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:

```html
<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:

```css
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:

```html
<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](https://cruip.com/tutorials/).