---
name: css-animation-creator
description: Create professional CSS animations, transitions, micro-interactions, and complex motion design. Use when adding animations, hover effects, loading states, page transitions, scroll animations, or any motion design work.
---
# CSS Animation Creator
## Instructions
When creating animations:
1. **Understand the purpose** - Feedback, delight, guidance, or storytelling
2. **Choose the right technique** - CSS transitions, keyframes, or JS libraries
3. **Optimize for performance** - GPU-accelerated properties only
4. **Respect accessibility** - Honor prefers-reduced-motion
5. **Keep timing natural** - Use appropriate easing and duration
## Animation Principles
### The 12 Principles (Disney) Applied to UI
| Principle | UI Application |
|-----------|----------------|
| **Squash & Stretch** | Button press, elastic effects |
| **Anticipation** | Hover states before action |
| **Staging** | Focus attention on important elements |
| **Follow Through** | Overshoot then settle |
| **Ease In/Out** | Natural acceleration/deceleration |
| **Arcs** | Curved motion paths |
| **Secondary Action** | Supporting animations |
| **Timing** | Duration conveys weight/importance |
| **Exaggeration** | Emphasis for clarity |
| **Appeal** | Pleasing, polished motion |
### Timing Guidelines
| Animation Type | Duration | Easing |
|----------------|----------|--------|
| Micro-interaction | 100-200ms | ease-out |
| Button/hover | 150-250ms | ease |
| Modal open | 200-300ms | ease-out |
| Modal close | 150-200ms | ease-in |
| Page transition | 300-500ms | ease-in-out |
| Loading loop | 1000-2000ms | linear/ease-in-out |
| Attention grab | 500-1000ms | elastic |
---
## CSS Transitions
### Basic Syntax
```css
.element {
/* Single property */
transition: opacity 0.3s ease;
/* Multiple properties */
transition:
transform 0.3s ease,
opacity 0.3s ease,
background-color 0.2s ease;
/* Shorthand: property duration timing-function delay */
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) 0s;
}
```
### Easing Functions
```css
/* Built-in */
transition-timing-function: linear;
transition-timing-function: ease; /* Default - slow start, fast middle, slow end */
transition-timing-function: ease-in; /* Slow start */
transition-timing-function: ease-out; /* Slow end */
transition-timing-function: ease-in-out; /* Slow start and end */
/* Custom cubic-bezier */
/* Material Design standard */
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
/* Decelerate (entering) */
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
/* Accelerate (exiting) */
transition-timing-function: cubic-bezier(0.4, 0, 1, 1);
/* Bounce effect */
transition-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55);
/* Elastic */
transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
```
### Tailwind Transitions
```tsx
// Duration
// 150ms
// 300ms
// 500ms
// Timing function
// Specific properties (better performance)
// Combined
```
---
## Keyframe Animations
### Basic Syntax
```css
@keyframes animationName {
0% { /* starting state */ }
50% { /* midpoint state */ }
100% { /* ending state */ }
}
.element {
animation: animationName 1s ease-in-out infinite;
/* name | duration | timing | iteration-count */
/* Full syntax */
animation-name: animationName;
animation-duration: 1s;
animation-timing-function: ease-in-out;
animation-delay: 0s;
animation-iteration-count: infinite; /* or number */
animation-direction: normal; /* reverse, alternate, alternate-reverse */
animation-fill-mode: forwards; /* none, forwards, backwards, both */
animation-play-state: running; /* paused */
}
```
### Essential Animations Library
#### Fade Animations
```css
/* Fade In */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* Fade In Up */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Fade In Down */
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Fade In Left */
@keyframes fadeInLeft {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
/* Fade In Right */
@keyframes fadeInRight {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
/* Fade In Scale */
@keyframes fadeInScale {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
/* Fade Out */
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
```
#### Scale Animations
```css
/* Scale In */
@keyframes scaleIn {
from { transform: scale(0); }
to { transform: scale(1); }
}
/* Scale In Bounce */
@keyframes scaleInBounce {
0% { transform: scale(0); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
/* Pop */
@keyframes pop {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
/* Pulse */
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
/* Heartbeat */
@keyframes heartbeat {
0%, 100% { transform: scale(1); }
14% { transform: scale(1.3); }
28% { transform: scale(1); }
42% { transform: scale(1.3); }
70% { transform: scale(1); }
}
```
#### Bounce Animations
```css
/* Bounce */
@keyframes bounce {
0%, 20%, 50%, 80%, 100% { transform: translateY(0); }
40% { transform: translateY(-20px); }
60% { transform: translateY(-10px); }
}
/* Bounce In */
@keyframes bounceIn {
0% {
opacity: 0;
transform: scale(0.3);
}
50% {
opacity: 1;
transform: scale(1.05);
}
70% { transform: scale(0.9); }
100% { transform: scale(1); }
}
/* Bounce In Down */
@keyframes bounceInDown {
0% {
opacity: 0;
transform: translateY(-100px);
}
60% {
opacity: 1;
transform: translateY(20px);
}
80% { transform: translateY(-10px); }
100% { transform: translateY(0); }
}
/* Rubber Band */
@keyframes rubberBand {
0% { transform: scaleX(1); }
30% { transform: scaleX(1.25) scaleY(0.75); }
40% { transform: scaleX(0.75) scaleY(1.25); }
50% { transform: scaleX(1.15) scaleY(0.85); }
65% { transform: scaleX(0.95) scaleY(1.05); }
75% { transform: scaleX(1.05) scaleY(0.95); }
100% { transform: scaleX(1) scaleY(1); }
}
```
#### Rotate Animations
```css
/* Spin */
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* Spin Reverse */
@keyframes spinReverse {
from { transform: rotate(360deg); }
to { transform: rotate(0deg); }
}
/* Swing */
@keyframes swing {
20% { transform: rotate(15deg); }
40% { transform: rotate(-10deg); }
60% { transform: rotate(5deg); }
80% { transform: rotate(-5deg); }
100% { transform: rotate(0deg); }
}
/* Wobble */
@keyframes wobble {
0% { transform: translateX(0); }
15% { transform: translateX(-15px) rotate(-5deg); }
30% { transform: translateX(12px) rotate(3deg); }
45% { transform: translateX(-9px) rotate(-3deg); }
60% { transform: translateX(6px) rotate(2deg); }
75% { transform: translateX(-3px) rotate(-1deg); }
100% { transform: translateX(0); }
}
/* Flip */
@keyframes flipX {
0% { transform: perspective(400px) rotateX(90deg); opacity: 0; }
40% { transform: perspective(400px) rotateX(-20deg); }
60% { transform: perspective(400px) rotateX(10deg); opacity: 1; }
80% { transform: perspective(400px) rotateX(-5deg); }
100% { transform: perspective(400px) rotateX(0deg); }
}
```
#### Slide Animations
```css
/* Slide In Up */
@keyframes slideInUp {
from {
transform: translateY(100%);
visibility: visible;
}
to { transform: translateY(0); }
}
/* Slide In Down */
@keyframes slideInDown {
from {
transform: translateY(-100%);
visibility: visible;
}
to { transform: translateY(0); }
}
/* Slide In Left */
@keyframes slideInLeft {
from {
transform: translateX(-100%);
visibility: visible;
}
to { transform: translateX(0); }
}
/* Slide In Right */
@keyframes slideInRight {
from {
transform: translateX(100%);
visibility: visible;
}
to { transform: translateX(0); }
}
/* Slide Out */
@keyframes slideOutUp {
from { transform: translateY(0); }
to {
transform: translateY(-100%);
visibility: hidden;
}
}
```
#### Attention Seekers
```css
/* Shake */
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
20%, 40%, 60%, 80% { transform: translateX(5px); }
}
/* Shake Horizontal (stronger) */
@keyframes shakeX {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-10px); }
20%, 40%, 60%, 80% { transform: translateX(10px); }
}
/* Jello */
@keyframes jello {
0%, 11.1%, 100% { transform: none; }
22.2% { transform: skewX(-12.5deg) skewY(-12.5deg); }
33.3% { transform: skewX(6.25deg) skewY(6.25deg); }
44.4% { transform: skewX(-3.125deg) skewY(-3.125deg); }
55.5% { transform: skewX(1.5625deg) skewY(1.5625deg); }
66.6% { transform: skewX(-0.78125deg) skewY(-0.78125deg); }
77.7% { transform: skewX(0.390625deg) skewY(0.390625deg); }
88.8% { transform: skewX(-0.1953125deg) skewY(-0.1953125deg); }
}
/* Flash */
@keyframes flash {
0%, 50%, 100% { opacity: 1; }
25%, 75% { opacity: 0; }
}
/* Tada */
@keyframes tada {
0% { transform: scale(1) rotate(0); }
10%, 20% { transform: scale(0.9) rotate(-3deg); }
30%, 50%, 70%, 90% { transform: scale(1.1) rotate(3deg); }
40%, 60%, 80% { transform: scale(1.1) rotate(-3deg); }
100% { transform: scale(1) rotate(0); }
}
```
---
## Loading Animations
### Spinners
```tsx
// Simple spinner
// Dual ring
// Gradient spinner
```
```css
/* Pulsing ring */
@keyframes pingRing {
0% {
transform: scale(1);
opacity: 1;
}
75%, 100% {
transform: scale(2);
opacity: 0;
}
}
.ping-ring {
position: relative;
}
.ping-ring::before {
content: '';
position: absolute;
inset: 0;
border: 2px solid currentColor;
border-radius: 50%;
animation: pingRing 1.5s cubic-bezier(0, 0, 0.2, 1) infinite;
}
```
### Dots Loading
```tsx
// Bouncing dots
// Pulsing dots
{[0, 1, 2].map((i) => (
))}
```
```css
/* Scaling dots */
@keyframes dotScale {
0%, 80%, 100% { transform: scale(0); }
40% { transform: scale(1); }
}
.dot-loader {
display: flex;
gap: 4px;
}
.dot-loader span {
width: 8px;
height: 8px;
background: currentColor;
border-radius: 50%;
animation: dotScale 1.4s ease-in-out infinite;
}
.dot-loader span:nth-child(1) { animation-delay: -0.32s; }
.dot-loader span:nth-child(2) { animation-delay: -0.16s; }
.dot-loader span:nth-child(3) { animation-delay: 0s; }
```
### Skeleton Loaders
```tsx
// Basic skeleton
// Card skeleton
// Shimmer effect
```
```css
/* Shimmer keyframe */
@keyframes shimmer {
100% { transform: translateX(100%); }
}
```
### Progress Bars
```tsx
// Indeterminate progress
// Striped progress
```
```css
@keyframes progress {
0% { transform: translateX(-100%); }
100% { transform: translateX(400%); }
}
@keyframes progress-stripes {
from { background-position: 1rem 0; }
to { background-position: 0 0; }
}
```
---
## Micro-interactions
### Button Effects
```tsx
// Press effect
// Ripple effect (React)
function RippleButton({ children, ...props }) {
const [ripples, setRipples] = useState([]);
const handleClick = (e) => {
const rect = e.currentTarget.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
setRipples([...ripples, { x, y, id: Date.now() }]);
setTimeout(() => setRipples(r => r.slice(1)), 600);
};
return (
);
}
```
```css
@keyframes ripple {
from {
width: 0;
height: 0;
opacity: 0.5;
}
to {
width: 200px;
height: 200px;
opacity: 0;
}
}
```
### Hover Effects
```tsx
// Lift effect
Card content
// Glow effect
// Border animation
// Underline animation
Animated link
// Fill animation
Hover me
```
### Icon Animations
```tsx
// Rotate on hover
// Bounce on hover
// Scale + rotate
```
### Form Interactions
```tsx
// Input focus effect
// Checkbox animation
// Toggle switch
```
### Success/Error States
```tsx
// Success checkmark
// Error shake
```
```css
@keyframes draw {
to { stroke-dashoffset: 0; }
}
```
---
## Page Transitions
### CSS-only Transitions
```css
/* View Transitions API (Chrome 111+) */
@view-transition {
navigation: auto;
}
::view-transition-old(root) {
animation: fadeOut 0.3s ease-out;
}
::view-transition-new(root) {
animation: fadeIn 0.3s ease-in;
}
/* Specific element transitions */
.hero-image {
view-transition-name: hero;
}
::view-transition-old(hero),
::view-transition-new(hero) {
animation-duration: 0.5s;
}
```
### Framer Motion
```tsx
import { motion, AnimatePresence } from 'framer-motion';
// Fade transition
const pageVariants = {
initial: { opacity: 0 },
animate: { opacity: 1 },
exit: { opacity: 0 },
};
function PageWrapper({ children }) {
return (
{children}
);
}
// Slide transition
const slideVariants = {
initial: { opacity: 0, x: 20 },
animate: { opacity: 1, x: 0 },
exit: { opacity: 0, x: -20 },
};
// Scale + fade
const scaleVariants = {
initial: { opacity: 0, scale: 0.95 },
animate: { opacity: 1, scale: 1 },
exit: { opacity: 0, scale: 1.05 },
};
// Shared layout animations
function Gallery({ items, selectedId }) {
return (
<>
{items.map(item => (
))}
{selectedId && (
i.id === selectedId).src} />
)}
>
);
}
```
### Staggered Animations
```tsx
// Framer Motion stagger
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
};
function StaggeredList({ items }) {
return (
{items.map(item => (
{item.name}
))}
);
}
// CSS stagger with custom properties
{items.map((item, i) => (
-
{item.name}
))}
```
```css
.stagger-list li {
animation: fadeInUp 0.5s ease forwards;
animation-delay: calc(var(--i) * 0.1s);
}
```
---
## Scroll Animations
### Intersection Observer
```tsx
function useInView(options = {}) {
const ref = useRef(null);
const [isInView, setIsInView] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
setIsInView(true);
if (options.once) observer.disconnect();
}
}, { threshold: 0.1, ...options });
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, []);
return [ref, isInView];
}
// Usage
function AnimatedSection() {
const [ref, isInView] = useInView({ once: true });
return (
Content
);
}
```
### Scroll-triggered with Framer Motion
```tsx
import { motion, useScroll, useTransform } from 'framer-motion';
function ParallaxSection() {
const ref = useRef(null);
const { scrollYProgress } = useScroll({
target: ref,
offset: ["start end", "end start"]
});
const y = useTransform(scrollYProgress, [0, 1], [100, -100]);
const opacity = useTransform(scrollYProgress, [0, 0.5, 1], [0, 1, 0]);
return (
Parallax content
);
}
// Scroll-linked progress
function ScrollProgress() {
const { scrollYProgress } = useScroll();
return (
);
}
```
### CSS Scroll-driven Animations
```css
/* Native scroll-driven animations (Chrome 115+) */
@keyframes reveal {
from {
opacity: 0;
transform: translateY(50px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.scroll-reveal {
animation: reveal linear both;
animation-timeline: view();
animation-range: entry 0% cover 40%;
}
/* Scroll progress indicator */
.progress-bar {
transform-origin: left;
animation: grow linear;
animation-timeline: scroll();
}
@keyframes grow {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
```
---
## Accessibility
### Reduced Motion
```css
/* Global reduced motion */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* Per-element control */
.animated-element {
animation: bounce 1s infinite;
}
@media (prefers-reduced-motion: reduce) {
.animated-element {
animation: none;
}
}
```
```tsx
// Tailwind motion-safe/motion-reduce
Respects preferences
// React hook
function usePrefersReducedMotion() {
const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
setPrefersReducedMotion(mediaQuery.matches);
const handler = (e) => setPrefersReducedMotion(e.matches);
mediaQuery.addEventListener('change', handler);
return () => mediaQuery.removeEventListener('change', handler);
}, []);
return prefersReducedMotion;
}
// Usage
function AnimatedComponent() {
const prefersReducedMotion = usePrefersReducedMotion();
return (
);
}
```
---
## Tailwind Animation Config
```javascript
// tailwind.config.js
module.exports = {
theme: {
extend: {
animation: {
// Fade
'fade-in': 'fadeIn 0.5s ease forwards',
'fade-in-up': 'fadeInUp 0.5s ease forwards',
'fade-in-down': 'fadeInDown 0.5s ease forwards',
'fade-out': 'fadeOut 0.3s ease forwards',
// Slide
'slide-in-left': 'slideInLeft 0.3s ease-out',
'slide-in-right': 'slideInRight 0.3s ease-out',
'slide-in-up': 'slideInUp 0.3s ease-out',
'slide-in-down': 'slideInDown 0.3s ease-out',
// Scale
'scale-in': 'scaleIn 0.2s ease-out',
'pop': 'pop 0.3s ease-out',
// Attention
'shake': 'shake 0.5s ease-in-out',
'wiggle': 'wiggle 1s ease-in-out infinite',
'heartbeat': 'heartbeat 1.5s ease-in-out infinite',
// Loading
'shimmer': 'shimmer 2s infinite',
'progress': 'progress 1s ease-in-out infinite',
},
keyframes: {
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
fadeInUp: {
'0%': { opacity: '0', transform: 'translateY(20px)' },
'100%': { opacity: '1', transform: 'translateY(0)' },
},
fadeInDown: {
'0%': { opacity: '0', transform: 'translateY(-20px)' },
'100%': { opacity: '1', transform: 'translateY(0)' },
},
fadeOut: {
'0%': { opacity: '1' },
'100%': { opacity: '0' },
},
slideInLeft: {
'0%': { transform: 'translateX(-100%)' },
'100%': { transform: 'translateX(0)' },
},
slideInRight: {
'0%': { transform: 'translateX(100%)' },
'100%': { transform: 'translateX(0)' },
},
slideInUp: {
'0%': { transform: 'translateY(100%)' },
'100%': { transform: 'translateY(0)' },
},
slideInDown: {
'0%': { transform: 'translateY(-100%)' },
'100%': { transform: 'translateY(0)' },
},
scaleIn: {
'0%': { transform: 'scale(0)' },
'100%': { transform: 'scale(1)' },
},
pop: {
'0%': { transform: 'scale(1)' },
'50%': { transform: 'scale(1.1)' },
'100%': { transform: 'scale(1)' },
},
shake: {
'0%, 100%': { transform: 'translateX(0)' },
'10%, 30%, 50%, 70%, 90%': { transform: 'translateX(-5px)' },
'20%, 40%, 60%, 80%': { transform: 'translateX(5px)' },
},
wiggle: {
'0%, 100%': { transform: 'rotate(-3deg)' },
'50%': { transform: 'rotate(3deg)' },
},
heartbeat: {
'0%, 100%': { transform: 'scale(1)' },
'14%': { transform: 'scale(1.3)' },
'28%': { transform: 'scale(1)' },
'42%': { transform: 'scale(1.3)' },
'70%': { transform: 'scale(1)' },
},
shimmer: {
'100%': { transform: 'translateX(100%)' },
},
progress: {
'0%': { transform: 'translateX(-100%)' },
'100%': { transform: 'translateX(400%)' },
},
},
},
},
};
```
---
## Performance Best Practices
### GPU-Accelerated Properties
```css
/* GOOD - GPU accelerated */
transform: translateX(100px);
transform: scale(1.1);
transform: rotate(45deg);
opacity: 0.5;
/* BAD - Triggers layout/paint */
left: 100px;
top: 50px;
width: 200px;
height: 100px;
margin: 20px;
padding: 10px;
border-width: 2px;
font-size: 16px;
```
### will-change (Use Sparingly)
```css
/* Only when needed for complex animations */
.complex-animation {
will-change: transform, opacity;
}
/* Remove after animation */
.complex-animation.done {
will-change: auto;
}
```
### Contain for Isolation
```css
.animated-section {
contain: layout style paint;
}
```
### Animation Performance Checklist
- [ ] Only animate `transform` and `opacity`
- [ ] Use `will-change` only when necessary
- [ ] Keep animations under 300ms for UI feedback
- [ ] Test on low-end devices
- [ ] Use `contain` for isolated sections
- [ ] Reduce animation during scroll
- [ ] Pause off-screen animations