Most Tailwind animations on the internet look the same: a `hover:scale-105` here, a `transition-all` there, maybe a `hover:shadow-purple-500` if someone's feeling adventurous. They work, but they don't feel crafted. Good animations have purpose - they communicate state changes, guide attention, and reward interaction. Here's how to do that with Tailwind.
# The Foundation: transition, duration, ease
Every animated element needs three things: what to animate, how long, and what easing curve. Tailwind's defaults are reasonable but generic. Be intentional:
<!-- Generic and forgettable -->
<button class="hover:bg-blue-600 transition-all duration-300">Click me</button>
<!-- Intentional - only animates background and shadow, custom easing -->
<button class="
bg-teal-600
hover:bg-teal-500
hover:shadow-[0_0_20px_rgba(13,148,136,0.4)]
transition-[background-color,box-shadow]
duration-200
ease-out
">
Click me
</button>`transition-all` animates every property including layout ones like `width` and `height` - this forces the browser to do expensive layout recalculations. Only animate `transform`, `opacity`, `background-color`, and `box-shadow` for 60fps smoothness.
# Card Hover Effects That Actually Look Good
<!-- Lift + glow - elegant for dark UIs -->
<div class="
group
rounded-xl
border border-white/5
bg-white/3
p-6
transition-all duration-300
hover:-translate-y-1.5
hover:border-teal-500/40
hover:shadow-[0_8px_30px_rgba(13,148,136,0.15)]
cursor-pointer
">
<!-- Arrow icon that moves on hover -->
<span class="
inline-block
transition-transform duration-200
group-hover:translate-x-1
group-hover:-translate-y-1
">
↗
</span>
</div># Custom Keyframe Animations in Tailwind
Tailwind's built-in animations are limited to `spin`, `ping`, `pulse`, and `bounce`. For anything custom, extend your config:
// tailwind.config.ts
export default {
theme: {
extend: {
keyframes: {
"fade-up": {
"0%": { opacity: "0", transform: "translateY(16px)" },
"100%": { opacity: "1", transform: "translateY(0)" },
},
"shimmer": {
"0%": { backgroundPosition: "-200% 0" },
"100%": { backgroundPosition: "200% 0" },
},
"blink": {
"0%, 100%": { opacity: "1" },
"50%": { opacity: "0" },
},
},
animation: {
"fade-up": "fade-up 0.5s ease-out forwards",
"shimmer": "shimmer 2s linear infinite",
"blink": "blink 1s step-end infinite",
},
},
},
};<!-- Skeleton loading shimmer effect -->
<div class="
h-4 w-48 rounded
bg-gradient-to-r from-white/5 via-white/10 to-white/5
bg-[length:200%_100%]
animate-shimmer
" />
<!-- Staggered fade-up for lists -->
<ul>
<li class="animate-fade-up [animation-delay:0ms]">Item 1</li>
<li class="animate-fade-up [animation-delay:100ms]">Item 2</li>
<li class="animate-fade-up [animation-delay:200ms]">Item 3</li>
</ul># The Typewriter Cursor (No Library Needed)
<!-- Blinking cursor using the custom blink animation -->
<p class="font-mono text-lg">
Full-Stack Developer
<span class="
inline-block w-0.5 h-5 ml-0.5
bg-teal-400
align-middle
animate-blink
" />
</p># Rules for Animations That Don't Annoy People
- Duration under 300ms for micro-interactions (hover, click). Longer feels laggy.
- Duration 400-700ms for page transitions and entrance animations. Longer feels cinematic (in a bad way).
- Use `ease-out` for things appearing - they start fast and settle. Use `ease-in` for things disappearing.
- Respect `prefers-reduced-motion` - wrap animations with `motion-safe:` prefix in Tailwind.
- Animate one thing at a time per element. Multiple simultaneous animations fight for attention.
<!-- Respects user's OS motion preference -->
<div class="motion-safe:animate-fade-up motion-reduce:opacity-100">
Accessible animation
</div>

