Tailwind Animation Utilities
Build smooth micro-interactions in Tailwind.
Animations make interfaces feel alive—great for feedback, onboarding, and micro-interactions. Tailwind ships helpful defaults (animate-spin
, animate-ping
, animate-pulse
, animate-bounce
) and in v4 you can define custom keyframes right in CSS with @theme
.
This guide shows the built-ins, how to add your own animations in v4, and how to use enter/exit patterns with tailwindcss-animate
or a v4-native CSS plugin.
Quick start: built-in animate classes
These cover common cases and are perfect for loaders, badges, skeletons, and cues.
<!-- Spinner -->
<svg class="size-5 animate-spin text-sky-600" viewBox="0 0 24 24" aria-hidden="true">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" fill="none" stroke-width="4"/>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 0116 0h-2a6 6 0 10-12 0H4z"/>
</svg>
<!-- Notification ping -->
<span class="relative inline-flex size-3">
<span class="absolute inline-flex h-full w-full animate-ping rounded-full bg-rose-400 opacity-75"></span>
<span class="relative inline-flex size-3 rounded-full bg-rose-500"></span>
</span>
<!-- Skeleton pulse -->
<div class="animate-pulse space-y-3">
<div class="h-3 rounded bg-gray-200"></div>
<div class="h-3 rounded bg-gray-200"></div>
<div class="h-3 rounded bg-gray-200"></div>
</div>
These utilities are documented in Tailwind’s Transitions & Animation → animation page, with examples and a quick reference.
What’s new in Tailwind v4 for animations
Tailwind v4 moved to a CSS-first configuration. Instead of defining animations in tailwind.config.js
, you define theme variables and @keyframes
inside a @theme
block, directly in your CSS. This makes custom animations feel first-class and tree-shakable.
Define a custom animation with @theme
/* app.css */
@import "tailwindcss";
/* 1) Define animation variable + keyframes */
@theme {
--animate-wiggle: wiggle 600ms ease-in-out infinite;
@keyframes wiggle {
0%, 100% { transform: rotate(-3deg) }
50% { transform: rotate(3deg) }
}
}
/* Optional: a one-off custom value via arbitrary class:
<div class="animate-[wiggle_0.6s_ease-in-out_infinite]"></div> */
<!-- 2) Use it like any other utility -->
<button class="inline-flex items-center gap-2 rounded px-3 py-2 bg-slate-900 text-white animate-wiggle">
Save
</button>
The --animate-*
variable creates an animate-*
utility, and keyframes inside @theme
are included in the generated CSS. You can also use animate-[…]
for one-off values or animate-(--my-animation)
to read from a CSS variable.
Enter/Exit animations (modals, drawers, toasts)
For “animate in/out” patterns, the community standard is tailwindcss-animate
(used widely with shadcn/ui). It exposes classes like animate-in
, fade-in
, zoom-in
, slide-in-from-*
, plus animate-out
variants.
Option A — classic plugin (v3/v4 compatibility)
If you still use JS config or prefer the original plugin:
npm i tailwindcss-animate
// tailwind.config.js or .ts
module.exports = {
// content: [...] (v3) — v4 can auto-detect
plugins: [require("tailwindcss-animate")],
};
<!-- Dialog entering -->
<div class="animate-in fade-in zoom-in duration-300">
<!-- content -->
</div>
<!-- Dialog leaving -->
<div class="animate-out fade-out zoom-out duration-200">
<!-- content -->
</div>
Examples and API live in the repo’s README. Note: the plugin reuses duration-*
and delay-*
to control animation timing for its utilities.
Option B — v4-native CSS import: tw-animate-css (drop-in style)
Prefer v4’s CSS-first approach? Use tw-animate-css
. It’s designed for Tailwind v4, imported straight in your CSS, and offers the same animate-in/out
vocabulary plus handy presets like accordion-down
.
npm i -D tw-animate-css
/* app.css */
@import "tailwindcss";
@import "tw-animate-css";
<!-- Toast -->
<div class="animate-in fade-in slide-in-from-top-8 duration-500">
Saved successfully
</div>
<!-- Accordion using data attributes -->
<div data-state="open"
class="data-[state=open]:animate-in data-[state=closed]:animate-out
fade-in slide-in-from-top-4 fade-out slide-out-to-top-4">
<!-- content -->
</div>
The package documents enter/exit primitives, parameter classes (duration-*
, delay-*
, repeat-*
), and ready-made animations (accordion-down
, caret-blink
).
Accessibility: respect user motion preferences
Use Tailwind’s motion-safe:
and motion-reduce:
variants to disable or downgrade motion when the OS asks for reduced animation. This applies to both transitions and animations:
<!-- Only spin if motion is allowed -->
<svg class="size-5 motion-safe:animate-spin"></svg>
<!-- Fall back to no animation for users who prefer less motion -->
<div class="motion-reduce:animate-none motion-reduce:transition-none">
…
</div>
Tailwind shows this pattern in docs, which itself reflects the prefers-reduced-motion
media feature from the platform.
Performance tips that actually matter
Prefer
transform
andopacity
. These animate on the compositor and avoid layout thrash.)Be cautious with
will-change
. It can help, but only on elements you truly animate—don’t sprinkle it everywhere.Duration & easing. Shorter, snappier animations (150–300 ms) feel responsive; use consistent easings (
ease-out
for exits,ease-in
for entrances) and tweak per component. Platform guides cover trade-offs and frame budgets (≈16.7 ms @ 60 fps).
Recipes you’ll reuse
1) Fade-in scale on mount (pure v4 CSS-first)
@import "tailwindcss";
@theme {
--animate-fade-in-scale: fade-in-scale 240ms ease-out both;
@keyframes fade-in-scale {
0% { opacity: 0; transform: scale(.96) }
100% { opacity: 1; transform: scale(1) }
}
}
<div class="animate-fade-in-scale">Card content</div>
This uses --animate-*
to expose animate-fade-in-scale
, exactly like Tailwind’s docs describe.
2) Modal enter/exit (v4 CSS import plugin)
@import "tailwindcss";
@import "tw-animate-css"; /* v4-native */
<!-- Enter -->
<div class="animate-in fade-in zoom-in-95 duration-200">...</div>
<!-- Exit -->
<div class="animate-out fade-out zoom-out-95 duration-150">...</div>
The tw-animate-css
docs show the animate-in/out
primitives plus duration-*
, delay-*
, etc., mapped to animation timing.
3) Ping badge with reduced motion fallback (core utility)
<span class="relative inline-flex size-3">
<span class="absolute inset-0 rounded-full bg-emerald-400 opacity-75 motion-safe:animate-ping"></span>
<span class="relative inline-flex size-3 rounded-full bg-emerald-500"></span>
</span>
This mirrors the animate-ping
example from Tailwind’s docs, plus motion-safe:
so it won’t animate for users who asked to reduce motion.
Common v4 gotchas
“Where did my
tailwind.config.js
go?” In v4, you can skip it entirely. Put your tokens and keyframes in@theme
in CSS. If you need compatibility or complex setup, the upgrade guide explains both paths.“My custom keyframes didn’t emit.” Define them inside
@theme
when you want Tailwind to generate utilities tied to--animate-*
. If you always want the keyframes regardless of usage, define them outside@theme
.“How do I add third-party animation utilities in v4?” Prefer CSS imports (
@import "tw-animate-css"
) or a v4-compatible@plugin
/CSS approach. The functions & directives page covers compatibility options.
When to use transitions instead
Not every effect needs an animation. For hover/focus micro-interactions, transitions are lighter:
<button
class="inline-flex items-center rounded bg-indigo-600 px-4 py-2 text-white
transition-transform duration-200 ease-out hover:scale-105 active:scale-95">
Add to cart
</button>
See Tailwind’s transition docs for property, duration, delay, and easing utilities.
TL;DR
Use core
animate-*
for spins/pings/pulses/bounces.In v4, define custom animations with
@theme
→--animate-*
+@keyframes
, then use your newanimate-*
utility.For enter/exit, use
tailwindcss-animate
or the v4-nativetw-animate-css
import.Respect reduced motion and stick to transform/opacity for performance.
Want me to drop this straight into your Tailkits blog with a mini “Try it” playground (buttons that toggle animate-in/out
states)?

Yucel is a digital product creator and content writer with a knack for full-stack development. He loves blending technical know-how with engaging storytelling to build practical, user-friendly solutions. When he's not coding or writing, you'll likely find him exploring new tech trends or getting inspired by nature.