---
name: animator
description: Animation and micro-interaction patterns for web interfaces. Use when adding transitions, animations, hover effects, loading states, or any motion to UI components.
---
# Motion Design
Create meaningful, performant animations that enhance user experience.
## Core Principles
### Purpose of Motion
- **Feedback** - Confirm user actions (button press, form submit)
- **Orientation** - Show where elements come from/go to
- **Focus** - Direct attention to important changes
- **Delight** - Add personality without slowing users down
### When NOT to Animate
- User has `prefers-reduced-motion` enabled
- Animation would delay critical actions
- Motion doesn't add meaning
- On low-powered devices
```css
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
```
## Timing & Easing
### Duration Guidelines
| Type | Duration | Use Case |
|------|----------|----------|
| Micro | 100-150ms | Button states, toggles, small feedback |
| Standard | 200-300ms | Most UI transitions, modals, dropdowns |
| Complex | 300-500ms | Page transitions, large reveals |
| Emphasis | 500ms+ | Onboarding, celebrations (use sparingly) |
### Easing Functions
```css
/* Natural motion - use for most UI */
--ease-out: cubic-bezier(0.0, 0.0, 0.2, 1); /* Decelerate */
--ease-in: cubic-bezier(0.4, 0.0, 1, 1); /* Accelerate */
--ease-in-out: cubic-bezier(0.4, 0.0, 0.2, 1); /* Both */
/* Expressive motion - entrances/exits */
--ease-spring: cubic-bezier(0.175, 0.885, 0.32, 1.275); /* Overshoot */
--ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55); /* Playful */
/* Quick reference */
ease-out: Elements entering (coming to rest)
ease-in: Elements exiting (accelerating away)
ease-in-out: Elements moving between states
```
### Tailwind Defaults
```html
duration-75 duration-100 duration-150 duration-200 duration-300 duration-500
ease-linear ease-in ease-out ease-in-out
```
## Common Patterns
### Button Interactions
```css
.button {
transition: transform 150ms ease-out,
box-shadow 150ms ease-out,
background-color 150ms ease-out;
}
.button:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.button:active {
transform: translateY(0) scale(0.98);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
```
```tsx
// Tailwind
```
### Fade & Scale Enter
```css
/* Modal/Dialog entrance */
@keyframes fadeScaleIn {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
.modal {
animation: fadeScaleIn 200ms ease-out;
}
```
### Slide Transitions
```css
/* Slide from bottom */
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Slide from side (for drawers) */
@keyframes slideInRight {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}
```
### Staggered List Animation
```tsx
// Framer Motion
{items.map((item, i) => (
))}
```
```css
/* CSS stagger with animation-delay */
.list-item {
opacity: 0;
animation: fadeSlideIn 300ms ease-out forwards;
}
.list-item:nth-child(1) { animation-delay: 0ms; }
.list-item:nth-child(2) { animation-delay: 50ms; }
.list-item:nth-child(3) { animation-delay: 100ms; }
/* ... or use CSS custom properties */
.list-item {
animation-delay: calc(var(--index) * 50ms);
}
```
### Loading States
```css
/* Pulse (skeleton loading) */
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.skeleton {
animation: pulse 2s ease-in-out infinite;
}
/* Spinner */
@keyframes spin {
to { transform: rotate(360deg); }
}
.spinner {
animation: spin 1s linear infinite;
}
/* Progress bar shimmer */
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
.shimmer {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
```
### Hover Reveals
```css
/* Image zoom on hover */
.image-container {
overflow: hidden;
}
.image-container img {
transition: transform 300ms ease-out;
}
.image-container:hover img {
transform: scale(1.05);
}
/* Underline grow */
.link {
position: relative;
}
.link::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 2px;
background: currentColor;
transform: scaleX(0);
transform-origin: right;
transition: transform 250ms ease-out;
}
.link:hover::after {
transform: scaleX(1);
transform-origin: left;
}
```
## Framer Motion Patterns
### Basic Animation
```tsx
import { motion } from 'framer-motion';
Content
```
### Variants for Complex Animations
```tsx
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.05,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
};
{items.map((item) => (
{item.name}
))}
```
### Layout Animations
```tsx
// Animate layout changes automatically
{isExpanded ? : }
// Shared layout animation (element morphing)
{/* This element animates between positions */}
```
### Gestures
```tsx
Press me
```
### AnimatePresence for Exit Animations
```tsx
import { AnimatePresence, motion } from 'framer-motion';
{isVisible && (
Modal content
)}
```
## GSAP Patterns
### Basic Animation
```tsx
import gsap from 'gsap';
// Simple tween
gsap.to('.element', {
x: 100,
opacity: 1,
duration: 0.3,
ease: 'power2.out'
});
// From animation
gsap.from('.element', {
y: 20,
opacity: 0,
duration: 0.3,
ease: 'power2.out'
});
```
### Timeline for Sequences
```tsx
const tl = gsap.timeline();
tl.from('.header', { y: -50, opacity: 0 })
.from('.content', { y: 20, opacity: 0 }, '-=0.2')
.from('.footer', { y: 20, opacity: 0 }, '-=0.2');
// Control the timeline
tl.play();
tl.pause();
tl.reverse();
```
### Stagger Animations
```tsx
gsap.from('.list-item', {
y: 20,
opacity: 0,
duration: 0.3,
stagger: 0.05,
ease: 'power2.out'
});
```
### ScrollTrigger
```tsx
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
gsap.from('.section', {
scrollTrigger: {
trigger: '.section',
start: 'top 80%',
end: 'bottom 20%',
toggleActions: 'play none none reverse'
},
y: 50,
opacity: 0,
duration: 0.6
});
```
### GSAP Easing
```tsx
// Power easings (1-4, higher = more dramatic)
ease: 'power1.out' // Subtle
ease: 'power2.out' // Standard (like ease-out)
ease: 'power3.out' // Pronounced
ease: 'power4.out' // Dramatic
// Special easings
ease: 'back.out(1.7)' // Overshoot
ease: 'elastic.out(1, 0.3)' // Bouncy
ease: 'bounce.out' // Bounce at end
```
### React Integration
```tsx
import { useGSAP } from '@gsap/react';
import gsap from 'gsap';
function Component() {
const containerRef = useRef(null);
useGSAP(() => {
gsap.from('.item', {
y: 20,
opacity: 0,
stagger: 0.1
});
}, { scope: containerRef });
return (
);
}
```
## Performance Tips
### Use Transform & Opacity
```css
/* Good - GPU accelerated */
transform: translateX(100px);
transform: scale(1.1);
transform: rotate(45deg);
opacity: 0.5;
/* Avoid animating - triggers layout */
width, height, top, left, margin, padding
```
### will-change Hint
```css
/* Use sparingly - only for known animations */
.animated-element {
will-change: transform, opacity;
}
/* Remove after animation */
.animated-element.done {
will-change: auto;
}
```
### Reduce Motion Query
```tsx
// React hook
const prefersReducedMotion = window.matchMedia(
'(prefers-reduced-motion: reduce)'
).matches;
// Framer Motion
```
## Quick Reference
| Element | Duration | Easing | Properties |
|---------|----------|--------|------------|
| Button hover | 150ms | ease-out | transform, shadow, bg |
| Toggle switch | 200ms | ease-out | transform |
| Dropdown open | 200ms | ease-out | opacity, transform |
| Modal enter | 250ms | ease-out | opacity, scale |
| Modal exit | 200ms | ease-in | opacity, scale |
| Page transition | 300ms | ease-in-out | opacity, transform |
| Toast enter | 300ms | spring | transform |
| Skeleton pulse | 2000ms | ease-in-out | opacity |
## Motion Checklist
- [ ] Animation has clear purpose (feedback, orientation, focus)
- [ ] Duration feels snappy (not sluggish)
- [ ] Easing matches motion type (ease-out for enters)
- [ ] Respects prefers-reduced-motion
- [ ] Only animates transform/opacity when possible
- [ ] Exit animations are faster than enters
- [ ] Stagger delays are subtle (30-50ms)
- [ ] No animation blocks user interaction