Go back

Bringing Motion to Tailwind CSS: Building an animation plugin at Rombo

In this post, I’ll share the journey of developing tailwindcss-motion, a plugin designed to seamlessly integrate animation into Tailwind CSS, allowing designers and developers to animate elements with minimal effort and maximum flexibility.

You can explore the plugin on our landing page or check out the code on GitHub. Let’s dive into why we built it, the challenges we faced, and the impact we aim to create.

Why We Created It

The vision for tailwindcss-motion started when @nicksmithr reached out to me to collaborate on Rombo's latest project: a Tailwind plugin specifically for adding animations.

A huge thank you to Nick, who has been a key part of bringing tailwindcss-motion to life. From early brainstorming to making key design decisions, Nick played a big part in shaping this plugin, ensuring it meets the needs of both designers and developers.

At Rombo, our mission is to bridge the gap between design and implementation, and this plugin represents an essential foundation in that journey. An engineer will (soon) use Rombo to preview animations, a designer can use the Figma plugin to animate, and it should be easy to implement the animations in popular frameworks like Tailwind.

Landing Page

The Role of Plugins in Tailwind CSS

I view Tailwind plugins as a way to create APIs that easily harness the power and performance of the CSS underneath.

In tailwindcss-motion, we wanted to use Tailwind's modular structure to bring that same experience to animations, structuring animations as a series of modular classes. This gives users the ability to control animation styles without defining keyframes from scratch.

Building an Animation Engine

I envision this plugin as an 'animation engine' that leverages PostCSS and Tailwind's functionalities to provide an enhanced user experience with seamless, under-the-hood optimizations.

Modular Animations

One of the goals was to make animations in tailwindcss-motion as modular as any other Tailwind utility.

It all starts with base animations such as scale, translate, and rotate, which can be combined and customized. Users can stack animations like motion-scale-in-0 with motion-translate-x-in-150 to create dynamic entrance animations in a single element.

1. Composing the Uncomposable

Inspired by Adam Wathan's idea of "composing the uncomposable", we used CSS variables to enable composability in animations. Instead of creating a utility for every single animation combination, CSS variables allow users to define animations with greater flexibility.

Here's an example of how to compose an animation using CSS variables under the hood:

animation: motion-scale-in calc(var(--motion-scale-duration) * var(--motion-scale-perceptual-duration-multiplier)) var(--motion-scale-timing) var(--motion-scale-delay) both;`

2. Modifying Animations

tailwindcss-motion also supports various modifiers, including:

Modifiers apply to all property animations by default but can also be overridden individually to allow modularity. For example:

<div class="motion-translate-x-in-150 motion-scale-in-0
            motion-ease-spring-bounciest/translate">
</div>

This example shows how to adjust the easing of only the translate animation while keeping the default easing for others (e.g., scale).

Spring Animations

Spring easings in tailwindcss-motion can help create a more natural animation feel. To make these spring easings function seamlessly, we resolved several challenges so that users don’t have to handle them manually.

1. Perceptual vs. Settling Duration for Springs

To achieve a realistic animation feel, I implemented a perceptual-duration approach for spring animations using CSS variables and calc(). You can read more about this approach in my post Effortless UI Spring Animations. This system lets users simply define a duration while the plugin calculates everything else.

Default
Spring Snappy
Spring Bouncier

2. Springs Don't Make Sense with Certain Properties

Spring easings don’t work well with properties like opacity or color changes, resulting in unexpected results. To ensure a consistent user experience, tailwindcss-motion reverts properties like opacity to default easing when using a spring or bounce easing.

<div class="motion-translate-x-in-150 motion-opacity-100
            motion-spring-bounciest">
</div>
With correction
Without correction

The Details

To meet the diverse needs of users, tailwindcss-motion includes essential features:

1. prefers-reduced-motion

After researching about motion sensitivity, I configured tailwindcss-motion to remove translate and scale animations for users with reduced motion settings, while still allowing non-moving properties (like opacity and color) to animate.

"Animation that involves only non-moving properties, like opacity, color, and blurs, are unlikely to be problematic." - Designing Safer Web Animation for Motion Sensitivity

This is the default, requiring no extra setup from users, though future updates might offer further customization.

A video showing the difference between reduced motion and no reduced motion

2. Presets

Presets provide a quick way to apply predefined animations directly, saving time and effort while ensuring a cohesive design. We had some presets already from Rombo’s Shopify App (which has around 2,000 Shopify stores already animating!) so tailwindcss-motion offers a selection of these, each tailored for common use cases.

Fade
Slide
Focus
Blur
Expand
Shrink
Pop
Shake
Bounce

What’s Next

The tailwindcss-motion plugin is just the beginning. We’ve already launched the Animator Builder, available here, and are working on features like scroll-triggered animations, looping animations, and 3D animations. Join us on this journey by signing up for our waitlist.

Thank you for reading until the end!