GPU-Accelerated Properties
Only these properties animate on the compositor thread (60fps):
| Property | Use |
|---|---|
transform | Move, rotate, scale (translateX, rotate, scale) |
opacity | Fade in/out |
Everything else triggers layout or paint. Avoid animating:
width,height,margin,padding(layout thrashing)top,left,right,bottom(use transform instead)border-width,font-size(expensive reflows)
/* ❌ Triggers layout every frame */
.slide { left: 100px; transition: left 0.3s; }
/* ✅ GPU accelerated */
.slide { transform: translateX(100px); transition: transform 0.3s; }
Reduced Motion
~5% of users experience vestibular disorders (dizziness, nausea from motion).
/* Only animate if user hasn't requested reduced motion */
@media (prefers-reduced-motion: no-preference) {
.animated { animation: slide-in 0.5s ease-out; }
}
/* Or disable for those who requested it */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
Keep subtle fades and color changes even with reduced motion. Remove parallax, bouncing, infinite loops.
Timing Functions
| Easing | Use case |
|---|---|
ease-out | Elements entering view (appears responsive) |
ease-in | Elements exiting view (accelerates away) |
ease-in-out | Elements moving within view |
linear | Only for spinners, progress bars, color cycling |
/* Custom bounce */
transition: transform 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
/* Material Design standard */
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
Duration Guidelines
| Type | Duration |
|---|---|
| Micro-interactions (hover, focus) | 100-200ms |
| Transitions (modals, dropdowns) | 200-300ms |
| Page transitions | 300-500ms |
| Complex choreography | 500-1000ms max |
Anything over 500ms feels sluggish. Shorter is usually better.
CSS Transitions vs Animations
Transitions: A to B state changes
.button { transform: scale(1); transition: transform 0.2s ease-out; }
.button:hover { transform: scale(1.05); }
Animations: Multi-step, auto-play, looping
@keyframes fadeSlideIn {
0% { opacity: 0; transform: translateY(20px); }
100% { opacity: 1; transform: translateY(0); }
}
.card { animation: fadeSlideIn 0.5s ease-out forwards; }
Use transitions for hover/focus states. Use animations for on-load effects and sequences.
will-change
Only use as optimization when you have specific performance problems:
/* ✅ Apply before animation starts, remove after */
.card:hover { will-change: transform; }
.card { will-change: auto; }
/* ❌ Never apply globally */
* { will-change: transform, opacity; } /* Wastes GPU memory */
Transition Property Specificity
/* ❌ Animates everything including unintended properties */
.card { transition: all 0.3s; }
/* ✅ Only animates what you need */
.card { transition: transform 0.3s, box-shadow 0.3s; }
all can cause unexpected animations on color, background, border changes.
React/Framework Traps
Exit animations require AnimatePresence:
/* ❌ Exit animation never plays */
{isVisible && <motion.div exit={{ opacity: 0 }} />}
/* ✅ Wrap conditional rendering */
<AnimatePresence>
{isVisible && <motion.div exit={{ opacity: 0 }} />}
</AnimatePresence>
Stable keys for list animations:
/* ❌ Index keys cause erratic animations */
{items.map((item, i) => <li key={i}>{item}</li>)}
/* ✅ Stable IDs */
{items.map(item => <li key={item.id}>{item.text}</li>)}
AutoAnimate parent must be unconditional:
/* ❌ Ref can't attach to conditional element */
{showList && <ul ref={parent}>...</ul>}
/* ✅ Parent always renders, children are conditional */
<ul ref={parent}>{showList && items.map(...)}</ul>
Library Selection
| Library | Size | Best for |
|---|---|---|
| CSS only | 0kb | Hover states, simple transitions |
| AutoAnimate | 3kb | Lists, accordions, toasts (90% of UI animations) |
| Motion | 22kb | Gestures, physics, scroll animations, complex choreography |
| GSAP | 60kb | Timelines, creative animation, scroll-triggered sequences |
Start with CSS. Add AutoAnimate for list animations. Only add Motion/GSAP for complex needs.
Common Mistakes
- Animating
width/heightinstead ofscale—causes layout thrashing - Infinite animations without pause control—no way to stop
- Same easing for enter and exit—use ease-out for enter, ease-in for exit
- Ignoring prefers-reduced-motion—causes physical discomfort
- Duration over 500ms—feels sluggish
transition: allcatching unintended properties- Missing AnimatePresence for exit animations in React