name: design-motion
description: >
Build one new animation (micro-interaction, page transition, scroll, hover)
with Motion, CSS, or GSAP. Use when adding one animation to a new surface. A
pass across an existing app → enhance-motion.
license: MIT
Motion Design Skill
Degree of freedom: MIXED. Purpose and timing [HIGH freedom]; existing
motion inventory and reduced-motion [LOW freedom — run exactly].
Create purposeful, performant animations that enhance UX without overwhelming users.
How to reason
- Inventory — existing motion library and
prefers-reduced-motion
- Purpose — feedback, orientation, focus, or (rare) delight
- Implement —
transform/opacity only; match the app's timing
- Motion-safe — reduced-motion becomes an instant state change
Worked example
Inventory: Framer Motion already on the modal; no reduced-motion branch.
Purpose: orientation — show the dialog arriving, not decoration.
Implement: opacity + scale 0.95→1, 200ms ease-out; no height animation.
Motion-safe: prefers-reduced-motion skips enter/exit; focus still moves.
Self-critique before reporting
- Purpose named — not motion for its own sake
- GPU-only — no width/height/top/left animation
- Reduced-motion proven — a reduce setting was actually tested
- Right owner — app-wide coherent motion pass →
enhance-motion
Check existing first [LOW freedom — run exactly]
Before adding ANY animation, verify:
- Check for existing animation utilities:
cat package.json | grep -i "framer-motion\|gsap\|animat"
rg "motion\.|animate\(|useSpring" --type tsx -l | head -10
- Check for existing CSS animations:
rg "@keyframes|animation:" --type css
cat tailwind.config.* | grep -A20 "animation\|keyframes"
- Check for animation preferences:
rg "prefers-reduced-motion" --type tsx --type css
Why: Maintain consistent animation language. Respect user motion preferences.
Animation Principles [HIGH freedom]
1. Purpose-Driven Motion
Every animation should serve a purpose:
- Feedback - Confirm user actions (button press, form submit)
- Orientation - Show where elements come from/go to
- Focus - Direct attention to important elements
- Delight - Add personality (use sparingly)
2. Performance Rules
- Use
transform and opacity only (GPU-accelerated)
- Avoid animating
width, height, top, left (trigger layout)
- Use
will-change sparingly and remove after animation
- Target 60fps - keep animations under 100ms for interactions
3. Springs, not bezier soup (2026)
- One spring vocabulary per app: spatial springs (position, size, radius) may
overshoot; effects springs (color, opacity) never do. Three speeds: fast for
small controls, default for sheets and panels, slow for full-screen changes
(Material 3 motion-physics system; Motion
type: "spring" on the web).
- React Native / Expo: this is Reanimated territory (
withSpring on the UI thread,
Reanimated 4 CSS-style transitions). The native recipe lives in
enhance-mobile-native-feel → references/stack-recipes.md; this skill stays web.
Framer Motion Patterns (React)
Basic Animations
import { motion } from 'framer-motion'
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: "easeOut" }}
>
Content
</motion.div>
Exit: exit={{ opacity: 0, scale: 0.95 }} at 0.2s. Both: references/motion-patterns.md §Basic Animations.
Hover & Tap Interactions
<motion.button
whileHover={{ scale: 1.02, boxShadow: "0 4px 20px rgba(0,0,0,0.1)" }}
whileTap={{ scale: 0.98 }}
transition={{ type: "spring", stiffness: 400, damping: 17 }}
>
Click me
</motion.button>
Staggered Lists
Parent variants with transition: { staggerChildren: 0.05 }; each motion.li uses item variants hidden → show (opacity + x). Code: references/motion-patterns.md §Staggered Lists.
Scroll-Triggered Animations
useScroll() → scrollYProgress; useTransform maps it to y and opacity on a motion.div style. Code: references/motion-patterns.md §Scroll-Triggered Animations.
Page Transitions (Next.js)
app/template.tsx ('use client') wraps children in a motion.div with y: 10 → 0 → -10 and 0.3s opacity. Code: references/motion-patterns.md §Page Transitions.
Layout Animations
// Shared layout animations
<motion.div layout layoutId="card">
{isExpanded ? <ExpandedCard /> : <CollapsedCard />}
</motion.div>
// Smooth height changes
<motion.div
layout
transition={{ layout: { duration: 0.3 } }}
>
{showMore && <AdditionalContent />}
</motion.div>
CSS-Only Animations
Tailwind Animations
<div className="animate-in fade-in duration-300">
<div className="animate-in slide-in-from-bottom-4 duration-500">
<div className="animate-in fade-in slide-in-from-bottom-4 duration-500 delay-150">
Custom animation / keyframes (float, pulse-slow, shimmer) in tailwind.config.ts: references/css-and-gsap.md §Tailwind Animations.
Loading Skeleton
<div className="animate-pulse space-y-4">
<div className="h-4 bg-muted rounded w-3/4" />
<div className="h-4 bg-muted rounded w-1/2" />
</div>
// Shimmer effect
<div className="relative overflow-hidden bg-muted rounded">
<div className="absolute inset-0 -translate-x-full animate-shimmer bg-gradient-to-r from-transparent via-white/20 to-transparent" />
</div>
GSAP for Complex Animations
gsap.registerPlugin(ScrollTrigger); inside useGSAP(…, { scope: containerRef }) build a gsap.timeline({ scrollTrigger: { trigger, start, end, scrub: 1 } }) and chain .from() steps with negative offsets ("-=0.5").
Code: references/css-and-gsap.md §GSAP for Complex Animations.
Micro-interaction reference
Options, not a checklist. This skill adds one animation, so pick the single item the purpose needs; unrequested hover, zoom, or reveal effects are scope creep.
Catalogue by surface (buttons, forms, navigation, cards, modals): references/motion-patterns.md §Micro-interaction catalogue.
Accessibility [LOW freedom — run exactly]
// Always respect reduced motion preference
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
// Framer Motion
<motion.div
initial={prefersReducedMotion ? false : { opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: prefersReducedMotion ? 0 : 0.3 }}
>
// CSS
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
// Tailwind
<div className="motion-safe:animate-bounce motion-reduce:animate-none">
Delight & gamification patterns
For playful interactions (bouncy buttons, magnetic elements, confetti, Konami code, satisfying toggles, progress milestones), see references/delight-interactions.md.
Validation [LOW freedom — do not skip]
After implementing animations:
- Performance → 60fps in Chrome DevTools Performance tab
- Reduced motion → Test with
prefers-reduced-motion: reduce
- Mobile → Test on actual device (not just emulator); native apps also check the OS reduce-motion switch, not only the CSS media query
- Purpose → Each animation serves a clear UX purpose
- Consistency → Timing/easing matches rest of app