Motion Design: Animating Interfaces Right
Animation isn't decoration. Purposeful motion guides attention, provides feedback, and improves perceived performance. Bad animation annoys users.
Purpose Before Motion
Every animation should have a reason:
- Feedback: Button presses, form validation, state changes.
- Attention: Draw focus to important elements.
- Continuity: Show relationships between states (expand/collapse).
- Context: Provide sense of space and navigation.
Animation without purpose is clutter.
Duration and Easing
Timing matters.
- Quick feedback: 100-200ms. Button tap, checkbox toggle.
- Page transitions: 300-500ms. Navigation, modal entry.
- Complex animations: 500ms-1s. Multi-step sequences.
Longer isn't better. 2-second animations feel slow unless they're part of a narrative sequence.
Easing functions control motion feel:
/* Linear: robotic, unnatural */
transition: opacity 0.3s linear;
/* ease-out: snappy, responsive */
transition: opacity 0.3s ease-out;
/* ease-in-out: natural, smooth */
transition: opacity 0.3s ease-in-out;
/* Cubic-bezier: fine control */
transition: opacity 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); /* bounce */
Common patterns:
- ease-out: Entry animations (element appears, stabilizes).
- ease-in: Exit animations (element fades away).
- ease-in-out: Continuous motion (scroll, drag).
Performance: Use GPU
Animate transform and opacity. These are GPU-accelerated and cheap.
/* Good: GPU accelerated */
.element {
transform: translateX(100px);
opacity: 0.5;
transition: transform 0.3s, opacity 0.3s;
}
/* Bad: CPU expensive */
.element {
left: 100px; /* Causes layout recalc */
width: 200px; /* Causes repaint */
transition: left 0.3s, width 0.3s;
}
Why? GPU animation doesn't trigger layout recalcs or repaints. CPU animations do, which blocks frames.
Accessibility: Respect Motion Preferences
Some users experience motion sickness. Respect prefers-reduced-motion:
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
Effectively disables animations. In JavaScript:
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
if (!prefersReducedMotion) {
// Add animations
} else {
// Use instant state changes
}
Common Patterns
Fade in: Element appears gradually.
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.element {
animation: fadeIn 0.3s ease-out;
}
Slide in: Element enters from edge.
@keyframes slideIn {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}
.element {
animation: slideIn 0.4s ease-out;
}
Scale up: Element grows from small to full size (feels energetic).
@keyframes scaleUp {
from { transform: scale(0.8); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
.element {
animation: scaleUp 0.3s ease-out;
}
Loading spinner: Continuous rotation (never finishes, user understands it's loading).
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.spinner {
animation: spin 1s linear infinite;
}
Animation Libraries
CSS alone works for simple animations (fade, slide, scale).
Framer Motion (React):
import { motion } from 'framer-motion'
export function Box() {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
>
Content
</motion.div>
)
}
Three.js for 3D animations (overkill for most UIs).
Interaction Patterns
Hover states: Subtle scale or color change signals interactivity.
button {
transition: transform 0.2s ease-out, background-color 0.2s;
}
button:hover {
transform: scale(1.05);
background-color: #0056b3;
}
Loading states: Spinner or skeleton screen indicates work in progress.
{loading ? <Skeleton /> : <Content />}
Confirmation animations: Checkmark after form submit feels satisfying.
{submitted && (
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ duration: 0.4, type: 'spring' }}
>
✓
</motion.div>
)}
Testing and Iteration
Animations feel different at different speeds. Test on:
- Slow networks (Chrome DevTools: throttle connection).
- Real devices (animations less performant on mobile).
- Accessible browsers (reduce-motion settings).
Iterate: If animation feels sluggish, reduce duration. If it feels cheap, add easing or layering.
Frequently asked questions
How long should animations be?
100-200ms for quick feedback (buttons), 300-500ms for transitions, 500ms-1s for complex sequences. Longer feels slow.
Should I animate width and height or use transform?
Use transform. Width/height changes trigger expensive layout recalcs. transform and opacity are GPU-accelerated and cheap.
How do I respect user motion preferences?
Check `prefers-reduced-motion` media query. Disable or significantly shorten animations for users who enable it.