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.
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 occurrencesCSS 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.
| Animation | Use Case | Lines of Code |
|---|---|---|
| fade-in | Element entrance | 3 lines |
| slide-in-up | Slide up into view | 4 lines |
| scale-in | Scale up on entry | 4 lines |
| fade-out | Element exit | 3 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
| Metric | Before | After |
|---|---|---|
| Animation CSS | ~15KB | ~0.5KB |
| Library dependency | tw-animate-css | None |
| Render-blocking time | +200–400ms | Negligible |
| Visual difference | None (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
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.