CSS Keyframe Animation Library: 20 Copy-Paste Effects
20 ready-to-use CSS keyframe animations you can copy-paste into any React or Tailwind project. Fades, bounces, slides, glows — no library needed.
Why Build a Personal CSS Keyframe Library
Honestly, most animation packages are overkill. You pull in Framer Motion or GSAP for a simple fade-in, and suddenly your bundle has grown by 40 KB. For the majority of UI effects you'll actually ship — entrances, pulses, shakes, glows — raw CSS keyframes do the job with zero runtime cost.
A personal keyframe library is just a file (or a few utility classes) you carry project to project. You define the @keyframes once, reference them wherever you need them, and nothing fights your existing setup. Works with Tailwind v4.0.2, plain CSS modules, or inline style objects in React. No config needed.
This article gives you 20 production-ready keyframe definitions, organized by category. Each one is self-contained — copy the block, drop it into your stylesheet or globals.css, and you're done. Some of them pair naturally with particles backgrounds or aurora effects if you want layered motion.
The Anatomy of a CSS Keyframe Animation
Before the snippets, a quick orientation. A @keyframes rule defines what happens over time. The animation shorthand property attaches it to an element. That's the whole model. Two moving parts.
The shorthand order that trips people up: animation: name duration timing-function delay iteration-count direction fill-mode. You don't need all of them. animation: fadeIn 0.3s ease is perfectly valid.
One thing worth knowing: animation-fill-mode: both is almost always what you want. It applies the first keyframe before the animation starts and holds the last keyframe after it ends. Without it, elements snap back to their original state — which is rarely the intended behavior. Set both as your default and override only when needed.
Fade and Slide Animations (Effects 1–6)
These are the bread-and-butter entrance effects. They're subtle enough to use on almost any element without the page feeling overdone.
Copy this block into your globals.css or equivalent entry stylesheet:
/* Effect 1 — Fade In */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* Effect 2 — Fade Out */
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
/* Effect 3 — Slide Up */
@keyframes slideUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
/* Effect 4 — Slide Down */
@keyframes slideDown {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); }
}
/* Effect 5 — Slide In Left */
@keyframes slideInLeft {
from { opacity: 0; transform: translateX(-24px); }
to { opacity: 1; transform: translateX(0); }
}
/* Effect 6 — Slide In Right */
@keyframes slideInRight {
from { opacity: 0; transform: translateX(24px); }
to { opacity: 1; transform: translateX(0); }
}
/* Usage */
.card-enter {
animation: slideUp 0.4s ease both;
}The 24px offset on the slide effects is intentional — it's noticeable enough to read without feeling dramatic. Feel free to bump it to 48px for hero sections.
Scale and Bounce Animations (Effects 7–12)
Scale animations draw attention. Use them on interactive elements — buttons, cards on hover, modal entrances. Bounce variants add personality. Just don't sprinkle them everywhere or the interface feels restless.
/* Effect 7 — Scale In */
@keyframes scaleIn {
from { opacity: 0; transform: scale(0.92); }
to { opacity: 1; transform: scale(1); }
}
/* Effect 8 — Scale Out */
@keyframes scaleOut {
from { opacity: 1; transform: scale(1); }
to { opacity: 0; transform: scale(0.92); }
}
/* Effect 9 — Pop */
@keyframes pop {
0% { transform: scale(1); }
50% { transform: scale(1.08); }
100% { transform: scale(1); }
}
/* Effect 10 — Bounce In */
@keyframes bounceIn {
0% { opacity: 0; transform: scale(0.7); }
60% { opacity: 1; transform: scale(1.05); }
80% { transform: scale(0.97); }
100% { transform: scale(1); }
}
/* Effect 11 — Bounce Y */
@keyframes bounceY {
0%, 100% { transform: translateY(0); }
40% { transform: translateY(-14px); }
60% { transform: translateY(-6px); }
}
/* Effect 12 — Rubber Band */
@keyframes rubberBand {
0% { transform: scale(1,1); }
30% { transform: scale(1.25, 0.75); }
40% { transform: scale(0.75, 1.25); }
60% { transform: scale(1.15, 0.85); }
100% { transform: scale(1, 1); }
}The rubberBand effect works especially well on icon buttons when triggered on click. Keep its animation-duration at 0.6s — any slower and it reads as a glitch rather than an interaction.
Glow, Pulse, and Shimmer Animations (Effects 13–17)
These are the ones people screenshot. Glows, shimmers, and pulsing halos make an interface feel alive without moving layout. They pair particularly well with dark mode UIs — the kind you get when you layer them over a spotlight effect.
/* Effect 13 — Glow Pulse */
@keyframes glowPulse {
0%, 100% { box-shadow: 0 0 0px rgba(139, 92, 246, 0); }
50% { box-shadow: 0 0 20px rgba(139, 92, 246, 0.6); }
}
/* Effect 14 — Border Glow */
@keyframes borderGlow {
0%, 100% { border-color: rgba(255,255,255,0.15); }
50% { border-color: rgba(139, 92, 246, 0.8); }
}
/* Effect 15 — Shimmer (skeleton loading) */
@keyframes shimmer {
0% { background-position: -400px 0; }
100% { background-position: 400px 0; }
}
.skeleton {
background: linear-gradient(
90deg,
rgba(255,255,255,0.04) 25%,
rgba(255,255,255,0.12) 50%,
rgba(255,255,255,0.04) 75%
);
background-size: 800px 100%;
animation: shimmer 1.4s infinite linear;
}
/* Effect 16 — Heartbeat */
@keyframes heartbeat {
0%, 100% { transform: scale(1); }
14% { transform: scale(1.1); }
28% { transform: scale(1); }
42% { transform: scale(1.1); }
70% { transform: scale(1); }
}
/* Effect 17 — Ring Expand */
@keyframes ringExpand {
0% { transform: scale(0.8); opacity: 1; }
100% { transform: scale(2.2); opacity: 0; }
}
.ring-indicator::after {
content: '';
position: absolute;
inset: 0;
border-radius: 50%;
border: 2px solid rgba(139, 92, 246, 0.6);
animation: ringExpand 1.6s ease-out infinite;
}The shimmer skeleton is worth paying attention to. Setting background-size: 800px 100% and animating background-position is far more performant than animating left or margin. The compositor handles it entirely on the GPU.
Attention-Seeking Animations (Effects 18–20)
Shake, jiggle, wobble — these exist to tell users something went wrong or needs attention. Keep them short. A shake animation running for more than 0.5s stops feeling like feedback and starts feeling punishing.
/* Effect 18 — Shake (form validation errors) */
@keyframes shake {
0%, 100% { transform: translateX(0); }
15% { transform: translateX(-8px); }
30% { transform: translateX(8px); }
45% { transform: translateX(-6px); }
60% { transform: translateX(6px); }
75% { transform: translateX(-3px); }
90% { transform: translateX(3px); }
}
/* Effect 19 — Wobble */
@keyframes wobble {
0% { transform: translateX(0); }
15% { transform: translateX(-10px) rotate(-4deg); }
30% { transform: translateX(8px) rotate(3deg); }
45% { transform: translateX(-6px) rotate(-2deg); }
60% { transform: translateX(4px) rotate(1deg); }
75% { transform: translateX(-2px); }
100% { transform: translateX(0); }
}
/* Effect 20 — Flash */
@keyframes flash {
0%, 50%, 100% { opacity: 1; }
25%, 75% { opacity: 0; }
}In React, trigger these by toggling a class rather than re-mounting. Add the class on error, remove it with an onAnimationEnd handler so it can fire again on the next error. Here's the pattern:
import { useState } from 'react';
export function ShakeInput() {
const [isShaking, setIsShaking] = useState(false);
const handleInvalid = () => {
setIsShaking(true);
};
return (
<input
className={isShaking ? 'shake-error' : ''}
onInvalid={handleInvalid}
onAnimationEnd={() => setIsShaking(false)}
style={isShaking ? { animation: 'shake 0.45s ease both' } : {}}
/>
);
}Using Keyframes with Tailwind v4 Custom Utilities
Tailwind v4.0.2 introduced first-class support for @keyframes and animation utilities inside @theme. You can define your entire keyframe library in the same config block and get autocomplete across your project. No more switching between your CSS file and component files to wire things up.
/* In your CSS entry file with Tailwind v4 */
@import 'tailwindcss';
@theme {
--animate-fade-in: fadeIn 0.3s ease both;
--animate-slide-up: slideUp 0.4s ease both;
--animate-bounce-in: bounceIn 0.5s ease both;
--animate-shimmer: shimmer 1.4s infinite linear;
--animate-shake: shake 0.45s ease both;
--animate-glow-pulse: glowPulse 2s ease-in-out infinite;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* ... rest of your keyframes ... */Once defined in @theme, Tailwind generates animate-fade-in, animate-slide-up, etc. as utility classes. Use them directly in JSX: <div className="animate-slide-up">. You also get hover:animate-pop, group-hover:animate-shimmer, and so on for free.
If you're still on Tailwind v3, the equivalent lives in tailwind.config.ts under theme.extend.keyframes and theme.extend.animation. The pattern is almost identical, just different file locations. Check out Tailwind vs CSS Modules if you're weighing which approach makes sense for your project architecture.
Performance Tips for Smooth 60fps Animations
Animating the wrong CSS properties is the fastest way to kill frame rate. The only properties that avoid layout and paint entirely are transform and opacity. Everything else — width, height, top, left, background-color, border-width — triggers at least a paint, often a full layout recalculation.
Add will-change: transform to elements you're about to animate if there's a noticeable paint flash on the first frame. Use it sparingly — it tells the browser to promote the element to its own compositor layer, which costs memory. Setting it on every animated element defeats the purpose.
Do you need to animate 200 items simultaneously? Consider animation-delay staggering over JavaScript-driven approaches. A simple CSS custom property trick: set style={{ '--i': index }} on each item and use animation-delay: calc(var(--i) * 60ms) in your stylesheet. No JS animation loop, no requestAnimationFrame, no dependency. The confetti effect is a good example of where this kind of staggering makes the difference between a janky explosion and a smooth one.
FAQ
Yes. Define the @keyframes in a .module.css file and reference the animation name as a string in your animation property. CSS Modules scopes class names but not @keyframes names, so if two modules define @keyframes fadeIn, they'll collide. Prefix each keyframe name with your component name to avoid that — e.g., @keyframes cardFadeIn.
Apply the animation class unconditionally and set animation-fill-mode: both. The animation fires when the component mounts because the element is inserted into the DOM with the class already present. If you need it to replay, remove and re-add the class using setTimeout(0) to force a reflow, or use the key prop trick: change the element's key to force React to unmount and remount it.
forwards holds the final keyframe state after the animation completes. both applies the first keyframe before the animation starts AND holds the last keyframe after it ends. For entrance animations where opacity starts at 0, use both — otherwise the element will flash at full opacity before the animation begins.
Use animation-play-state: paused on the :hover selector. Example: .animated:hover { animation-play-state: paused; }. This works on infinite animations like glowPulse or shimmer. The animation resumes from where it left off when the cursor leaves.
Always wrap animations in a media query: @media (prefers-reduced-motion: no-preference) { ... }. Users who have enabled reduced motion in their OS settings will skip the animation entirely. For critical UI feedback like shake on form errors, consider replacing the motion with a color change instead — red border at rgba(239,68,68,0.8) conveys the same error state without movement.
Yes, and it's one of the most underused patterns. You can define --translate-y: 20px on a parent element and reference it inside the keyframe with transform: translateY(var(--translate-y)). This lets you control animation magnitude from JavaScript by updating the custom property, without touching the animation definition itself.