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
transformandopacity. 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-outfor exits,ease-infor 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.jsgo?” In v4, you can skip it entirely. Put your tokens and keyframes in@themein CSS. If you need compatibility or complex setup, the upgrade guide explains both paths.“My custom keyframes didn’t emit.” Define them inside
@themewhen 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-animateor the v4-nativetw-animate-cssimport.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.
