✳️ 681+ shadcn/ui Blocks, lifetime updates, unlimited projects — Get 50+ fresh blocks every month ✳️ 👇
See Components →

Tailwind Animation Utilities

Build smooth micro-interactions in Tailwind.

by Yucel F. Sahan
13 min read
Updated on

Tailwind Animation Utilities

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 and opacity. 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 new animate-* utility.

  • For enter/exit, use tailwindcss-animate or the v4-native tw-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 F. Sahan

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.