treeru.com

When you want animations in Tailwind CSS, tw-animate-css is usually the first thing that comes to mind. A single npm install gives you fadeIn, slideUp, bounce, and more as Tailwind utility classes.

The catch: a single import adds ~15KB of CSS to your bundle. Even if you only use 2–3 utilities, you ship all of them.

~15KB
Full import
~0.5KB
Only what you need
3–5
Utilities actually used
97%
Reduction

1The Problem — 15KB of Extra CSS

Check the "Eliminate render-blocking resources" section in PageSpeed Insights and you will see CSS bundle size flagged. Analyzing the built CSS revealed that animation-related code alone accounted for 15KB.

// globals.css — one line adds 15KB
@import "tw-animate-css";

// Classes actually used in the project:
// animate-fade-in        → 3 occurrences
// animate-slide-in-up    → 2 occurrences
// animate-fade-out       → 1 occurrence
// remaining 50+ utilities → 0 occurrences

CSS is a render-blocking resource

Unlike JavaScript, CSS is render-blocking by default. The browser will not paint anything until all CSS is downloaded and parsed. Adding 15KB of unused CSS delays FCP (First Contentful Paint). On a mobile 3G connection, 15KB translates to roughly 200–400ms of additional delay.

2Inside tw-animate-css

tw-animate-css ships 50+ animations. Internally, each one defines a @keyframes block, a @utility rule, and CSS custom properties.

/* tw-animate-css internals — definition for fadeIn alone */
@keyframes fade-in {
  from {
    opacity: 0;
  }
}

@utility animate-fade-in {
  animation: fade-in var(--tw-animate-duration, 0.3s)
    var(--tw-animate-easing, ease)
    var(--tw-animate-delay, 0)
    var(--tw-animate-iteration, 1)
    var(--tw-animate-fill, both);
}

/* + slideIn, slideOut, bounce, shake, spin,
   wiggle, pulse, ping, zoom... 50+ more */

Tailwind CSS v4 does remove unused utilities, but @keyframes are global definitions, not utilities — they are not subject to tree-shaking. Every @keyframes block ends up in the CSS bundle regardless of whether it is used.

3Finding Actually Used Classes

First, identify which animation classes your project actually uses.

# Search for animate- class usage across the project
grep -r "animate-" --include="*.tsx" --include="*.ts" \
  -oh | sort | uniq -c | sort -rn

# Example output:
#  12 animate-pulse     ← Built into Tailwind
#   6 animate-fade-in   ← tw-animate-css
#   3 animate-slide-in-up
#   1 animate-fade-out
#   ... (everything else: 0 uses)

Note: animate-pulse is built into Tailwind

animate-pulse, animate-spin, animate-bounce, and animate-ping are built-in Tailwind utilities — they work without tw-animate-css. Exclude these from your search results and you may find you need even fewer library animations than you thought.

4Extracting Only What You Need

If you only use 3 utilities, there is no reason to import the entire library. Copy just those definitions into your globals.css.

/* globals.css — Before */
@import "tw-animate-css";  /* ~15KB total */

/* globals.css — After: only what you need */
/* @import "tw-animate-css";  ← removed */

/* fadeIn — used in 3 places */
@keyframes fade-in {
  from { opacity: 0; }
}
@utility animate-fade-in {
  animation: fade-in 0.3s ease both;
}

/* slideInUp — used in 2 places */
@keyframes slide-in-up {
  from { transform: translateY(20px); opacity: 0; }
}
@utility animate-slide-in-up {
  animation: slide-in-up 0.4s ease both;
}

/* fadeOut — used in 1 place */
@keyframes fade-out {
  to { opacity: 0; }
}
@utility animate-fade-out {
  animation: fade-out 0.3s ease both;
}

15KB drops to roughly 500 bytes. And you can remove the library dependency entirely.

5Writing @keyframes From Scratch

Instead of extracting from the library, you can write them from scratch. The animations commonly used on the web boil down to a handful.

AnimationUse CaseLines of Code
fade-inElement entrance3 lines
slide-in-upSlide up into view4 lines
scale-inScale up on entry4 lines
fade-outElement exit3 lines

Most web animations are just opacity + transform combinations. These two properties are GPU-accelerated, so they perform well too. Complex bounce or shake animations are rarely needed in production.

/* Tailwind v4 — custom utility definitions */

/* Custom properties for duration/delay control */
@property --tw-animate-duration {
  syntax: "<time>";
  inherits: false;
  initial-value: 0.3s;
}

@property --tw-animate-delay {
  syntax: "<time>";
  inherits: false;
  initial-value: 0s;
}

/* Usage example: */
/* <div className="animate-fade-in [--tw-animate-duration:0.5s]"> */

The library is fine when you need it

If your project uses 10+ different animations, tw-animate-css is convenient. The problem is paying 15KB for 3–4 classes. Also consider team size and maintenance cost — hand-written @keyframes may prompt "what is this?" questions from teammates.

6Results

MetricBeforeAfter
Animation CSS~15KB~0.5KB
Library dependencytw-animate-cssNone
Render-blocking time+200–400msNegligible
Visual differenceNone (identical animations)

15KB dropped to 0.5KB. This alone will not dramatically change your PageSpeed score, but combined with web font optimization (26KB to 3KB), you eliminate roughly 38KB of render-blocking CSS. On a mobile 3G connection, that difference directly impacts FCP.

CSS Bundle Optimization Checklist

Before importing an animation library wholesale, count how many classes you actually use
If it is 3–4 classes, define just those @keyframes + @utility rules yourself
Do not confuse built-in Tailwind utilities (pulse, spin, bounce, ping) with library ones
@keyframes are not tree-shakeable — unused keyframes still end up in the bundle
Render-blocking CSS directly impacts FCP — every KB matters

The numbers in this article are based on tw-animate-css v1. Bundle size may vary with different library versions. Written for Tailwind CSS v4.