Animations with Tailwind CSS

Transition, the hidden star of Headless UI.

Markus Tripp
6 min readDec 28, 2022

The best mobile apps, like Airbnb and Spotify, are fun to use. But what makes them so great? Yes, they have an outstanding UI and UX. But what separates them are these little animations on page transitions and for other user interactions.

Airbnb animations on mobile (iPhone)
Airbnb animations on mobile (iPhone)

I create web apps, and most of the time, I use Tailwind CSS for styling.

How can I add those little animations to my web projects with Tailwind CSS?

Of course, I can use a library like Framer Motion. It’s fantastic, but in this article, I will show you how you can use the amazing Headless UI Transition component in combination with Tailwind CSS native animation properties to achieve great results.

I’m implementing two common use cases:

Animated form input elements in a slide-over panel

Tailwind CSS animation: Animated form input elements that slide over

Elements that fade in when they enter the viewport

Tailwind CSS animation: Elements that fade in when they enter the viewport
Elements that fade in when they enter the viewport

Find the source code of all steps from the beginning to the final working examples on my public GitHub repository:
markustripp/tailwind-animation.

Animated Input Elements

I assume you are familiar with setting up a Next.js project with Tailwind CSS, Tailwind Forms, and Headless UI. If not, tons of tutorials show you how to do that.

Step 1: Create Page Structure

First, create a page with two simple components: a Button component and an Input component. Clicking the button toggles the input field.

I’ll name the example pages slide1.js, slide2.js, slide3.js, … each number for each step during the development.

Clicking a button toggles the input field
Example Step 1: Create Page Structure
https://github.com/markustripp/tailwind-animation/blob/main/pages/slide1.js

Step 2: Add Headless UI Transition

Now, when clicking the button, I want to trigger the animation and fade in the input field. You control this by linking the Transition show attribute with the show state variable.

For the fade-in animation, you set the following attributes:

  • Opacity from zero to 100%
  • Translate-y from 6 to zero to move the element up during fading in
Clicking the button triggers the input field to fade-in.
Example Step 2: Add Headless UI Transition
https://github.com/markustripp/tailwind-animation/blob/main/pages/slide2.js

Step 3: Introducing Transition.Root and Transition.Child

On button click, I want to animate multiple form elements individually. Therefore I must convert the Transition component to Transition.Child components and wrap it with a Transition.Root component.

The Transition.Rootcomponent is linked to the show state variable and controls when all child components get triggered.

https://github.com/markustripp/tailwind-animation/blob/main/pages/slide3.js

Step 4: Create FadeIn Component

For re-usability and readability, I convert the Transition.Child animation into a FadeIn component. This enables me to apply the FadeIn component to multiple fields easily.

Clicking the button triggers the input fields to fade-in individually.
Example Step 4: Create FadeIn Component
https://github.com/markustripp/tailwind-animation/blob/main/pages/slide4.js

Please note that each input field starts the animation with a different delay:

  • First name: delay-[0ms]
  • Last name: delay-[300ms]
  • Email: delay-[500ms]

Note: For the Tailwind CSS compiler to work properly, the attributes must be available in clear text. E.g., it is not possible to use variables for the milliseconds like delay-[${index * 100}ms]

Step 5: Add Background and SlideOver layer

Next, I want a panel with the form to slide in when the button is clicked. To create this effect, you must create two extra layers:

  • BackgroundLayer
  • SlideOverLayer with a panel that fades in the form input fields

To prevent scrolling of the underlying layer, I use a combination of fixed inset-0 and absolute properties.

Slide over panel when button is clicked
Example Step 5: Add Background and SlideOver layer
https://github.com/markustripp/tailwind-animation/blob/main/pages/slide5.js

Step 6: Animate Form Elements on SlideOverLayer

As the final step, I add an animation to the BackgroundLayer and SlideOverLayer and fade in the form input fields.

Tailwind CSS animation: Animated form input elements that slide over
Example Step 6: Animate Form Elements on SlideOverLayer
https://github.com/markustripp/tailwind-animation/blob/main/pages/slide6.js

In addition, you should add a Dialog component to the slide-over layer as described in the Tailwind UI component library.

Library Ideas

Steps 1 to 6 helped me create my own component library for my projects.

But slide7.jsand slide8.js include some ideas on how to structure and name a general animation library using this technique. Feel free to add your ideas in the comments below.

Creating such a library is technically pretty simple. The hard part is naming it properly and adding default animations that are smooth and look awesome on many different use cases.

Please note: slide7.js and slide8.js are just ideas and NOT ready libraries and NOT tested! Don’t report bugs in the comments.

This example replaces the Transition.Root component with an Animate component. And a general purpose SlideOver is introduced and can be re-used in many projects.

The children of this Animate.Group component are faded in using a linear timing.

Animate on Entering Viewport

Another common use case is to trigger an animation when an element or elements enter the viewport. Either the animation starts immediately or with an offset.

This example fades in a box that includes a header and paragraph when the box enters the viewport. Each element is animated individually.

Tailwind CSS animation: Elements that fade in when they enter the viewport
Final Result: Animate Box and Box Elements on Entering the Viewport

First, I must get notified when an element enters the viewport. All modern browsers implement the Intersection Observer API. But instead of using the native API, I use the react-intersection-observer library.

For this use case, I used the InView component. It contains a boolean property named inView. This property is true when the referenced ref element is inside the viewport and false otherwise. Now I link the inView property with the show property of the Transition.Root component.

Step 1: Create Page Structure

I start creating the page without the animation effect. It is just a centered box within a container. For this example, the container has the height of the entire screen.

Page with two boxes.
Example Step 1: Create Page Structure
https://github.com/markustripp/tailwind-animation/blob/main/pages/scroll1.js

Step 2: Add Animation

Similar to the slide-over example, I add a Transition.Root and multiple Transition.Child components.

When the second box enters the viewport, the inView variable is set to true, which triggers the animation of the Transition.Child elements in the box.

Tailwind CSS animation: Elements that fade in when they enter the viewport
Example Step 2: Add Animation
https://github.com/markustripp/tailwind-animation/blob/main/pages/scroll2.js

Step 3: Library Ideas

A possible usage of an animation library could look like that:

Conclusion

You will be surprised by what you can do with just the standard tools from Headless UI and Tailwind CSS. If you have advanced requirements for your animations, you can use a library like Framer Motion. But most of the time, using the standard tools is sufficient.

Over time, I created a set of animation components that I use across my projects.

About the author (Markus Tripp):
I’m a freelance web developer and Shopify consultant — and I’m the creator of Headcode CMS (www.headcodecms.com), a 100% open-source headless CMS for Next.js 13 App Router, Server Components, and Server Actions. Watch the video below for a quick product demo:

--

--