Back to list
NaveedTechLab

motion-interaction-designer

by NaveedTechLab

0🍴 0📅 Jan 11, 2026

SKILL.md


name: motion-interaction-designer description: Design smooth UI animations and interactions using Framer Motion for React/Next.js. Use when users request animations, transitions, hover effects, scroll animations, gesture interactions, or need help implementing Framer Motion. Follow modern motion UX principles with purposeful, performant animations that enhance usability.

Motion & Interaction Designer

Design purposeful, smooth UI animations using Framer Motion following modern motion UX principles.

Core Philosophy

Motion UX Principles:

  • Purposeful: Every animation serves a function (feedback, guidance, delight)
  • Subtle: Motion enhances, never distracts (200-500ms sweet spot)
  • Responsive: Respect user preferences (reduced motion)
  • Performant: 60fps on all devices, GPU-accelerated properties
  • Consistent: Unified timing and easing across the app

When to Animate:

  • ✅ State changes (hover, active, disabled)
  • ✅ Page/component transitions (route changes, modals)
  • ✅ User feedback (button clicks, form submissions)
  • ✅ Content reveals (scroll, load, stagger)
  • ✅ Contextual hints (first-time user guidance)

When NOT to Animate:

  • ❌ Critical actions (payment buttons - instant only)
  • ❌ Reading content (body text, paragraphs)
  • ❌ Rapid repetition (typing, scrolling lists)
  • ❌ User requested reduced motion

Technology Stack

Required Dependency:

{
  "framer-motion": "^11.0.0"
}

Optional (Scroll Animations):

{
  "react-intersection-observer": "^9.5.0"
}

Motion Workflow

When a user requests animation design, follow this approach:

Step 1: Identify Animation Type

Categorize the interaction into one of these patterns:

1. State Transitions (hover, focus, active, disabled)

  • Buttons, links, cards
  • Duration: 150-300ms
  • Easing: ease-out

2. Enter/Exit Animations (mount, unmount, visibility)

  • Modals, dropdowns, tooltips
  • Duration: 200-400ms
  • Easing: ease-in-out

3. Layout Animations (position, size changes)

  • Expanding cards, reordering lists
  • Duration: 300-500ms
  • Easing: spring or ease-out

4. Scroll Animations (scroll-triggered reveals)

  • Parallax, fade-ins, stagger effects
  • Duration: 400-600ms
  • Easing: ease-out

5. Gesture Interactions (drag, tap, swipe)

  • Custom controls, sliders, carousels
  • Duration: Immediate feedback + 200-400ms settle
  • Easing: spring (feels natural)

Step 2: Define Motion Properties

For each animation, specify:

  1. Properties to Animate:

    • GPU-accelerated (best performance): transform, opacity
    • Avoid: width, height, top, left, margin
  2. Duration: Target time in milliseconds

  3. Easing: Curve type (see timing guide below)

  4. Delay: Stagger or sequence timing

  5. Spring vs Tween: Natural feel vs precise control

Step 3: Provide Implementation Code

Structure code with:

  • Import statements
  • Component structure
  • Motion variants (reusable animation configs)
  • Responsive behavior
  • Accessibility (reduced motion)

Implementation Patterns

Pattern 1: Basic Hover Animation (Button)

Use Case: Call-to-action buttons, interactive elements

Implementation:

import { motion } from 'framer-motion'

export function AnimatedButton({ children, onClick }) {
  return (
    <motion.button
      onClick={onClick}
      whileHover={{
        scale: 1.05,
        boxShadow: '0 10px 20px rgba(0, 0, 0, 0.15)'
      }}
      whileTap={{ scale: 0.95 }}
      transition={{
        type: 'spring',
        stiffness: 400,
        damping: 17
      }}
      style={{
        padding: '12px 24px',
        backgroundColor: '#6366F1',
        color: 'white',
        border: 'none',
        borderRadius: '8px',
        cursor: 'pointer'
      }}
    >
      {children}
    </motion.button>
  )
}

Motion Properties:

  • Hover: Scale 1.05 (5% larger), shadow grows
  • Tap: Scale 0.95 (5% smaller) - tactile feedback
  • Transition: Spring (natural bounce)
  • Performance: GPU-accelerated (scale, boxShadow)

Pattern 2: Fade In on Mount (Modal, Dropdown)

Use Case: Overlays, popups, tooltips

Implementation:

import { motion, AnimatePresence } from 'framer-motion'

export function Modal({ isOpen, onClose, children }) {
  return (
    <AnimatePresence>
      {isOpen && (
        <>
          {/* Backdrop */}
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            transition={{ duration: 0.2 }}
            onClick={onClose}
            style={{
              position: 'fixed',
              inset: 0,
              backgroundColor: 'rgba(0, 0, 0, 0.5)',
              zIndex: 40
            }}
          />

          {/* Modal Content */}
          <motion.div
            initial={{ opacity: 0, scale: 0.95, y: 20 }}
            animate={{ opacity: 1, scale: 1, y: 0 }}
            exit={{ opacity: 0, scale: 0.95, y: 20 }}
            transition={{
              duration: 0.3,
              ease: [0.4, 0, 0.2, 1] // Custom ease-out
            }}
            style={{
              position: 'fixed',
              top: '50%',
              left: '50%',
              transform: 'translate(-50%, -50%)',
              backgroundColor: 'white',
              padding: '32px',
              borderRadius: '12px',
              zIndex: 50
            }}
          >
            {children}
          </motion.div>
        </>
      )}
    </AnimatePresence>
  )
}

Motion Properties:

  • Enter: Fade + scale up + slide down
  • Exit: Fade + scale down + slide down
  • Duration: 300ms (perceivable but not slow)
  • Easing: Custom ease-out (smooth deceleration)
  • AnimatePresence: Handles unmount animations

Pattern 3: Stagger Children (List Items)

Use Case: Todo lists, navigation items, feature grids

Implementation:

import { motion } from 'framer-motion'

const containerVariants = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: {
      staggerChildren: 0.1, // 100ms delay between children
      delayChildren: 0.2    // Wait 200ms before starting
    }
  }
}

const itemVariants = {
  hidden: { opacity: 0, x: -20 },
  visible: {
    opacity: 1,
    x: 0,
    transition: {
      duration: 0.4,
      ease: 'easeOut'
    }
  }
}

export function TodoList({ todos }) {
  return (
    <motion.ul
      variants={containerVariants}
      initial="hidden"
      animate="visible"
      style={{ listStyle: 'none', padding: 0 }}
    >
      {todos.map((todo) => (
        <motion.li
          key={todo.id}
          variants={itemVariants}
          style={{ padding: '12px', marginBottom: '8px' }}
        >
          {todo.title}
        </motion.li>
      ))}
    </motion.ul>
  )
}

Motion Properties:

  • Stagger: 100ms between each child
  • Delay: 200ms before first child
  • Item animation: Fade + slide from left
  • Variants: Reusable animation configs

Pattern 4: Scroll-Triggered Animation

Use Case: Section reveals, parallax effects

Implementation:

import { motion, useScroll, useTransform } from 'framer-motion'
import { useRef } from 'react'

export function ScrollReveal({ children }) {
  const ref = useRef(null)

  return (
    <motion.div
      ref={ref}
      initial={{ opacity: 0, y: 50 }}
      whileInView={{ opacity: 1, y: 0 }}
      viewport={{ once: true, amount: 0.3 }} // Trigger at 30% visible
      transition={{ duration: 0.6, ease: 'easeOut' }}
    >
      {children}
    </motion.div>
  )
}

// Advanced: Parallax scroll
export function ParallaxSection() {
  const { scrollYProgress } = useScroll()
  const y = useTransform(scrollYProgress, [0, 1], ['0%', '50%'])

  return (
    <motion.div
      style={{ y }}
      className="background-layer"
    >
      {/* Background content */}
    </motion.div>
  )
}

Motion Properties:

  • whileInView: Animates when 30% visible
  • viewport.once: Only animate first time (performance)
  • Parallax: Transform Y based on scroll progress
  • Use case: Hero sections, feature reveals

Pattern 5: Layout Animation (Reordering, Expanding)

Use Case: Animated lists, accordions, grid layouts

Implementation:

import { motion, LayoutGroup } from 'framer-motion'
import { useState } from 'react'

export function AnimatedList({ items }) {
  const [sortedItems, setSortedItems] = useState(items)

  return (
    <LayoutGroup>
      <motion.ul layout>
        {sortedItems.map((item) => (
          <motion.li
            key={item.id}
            layout
            transition={{
              type: 'spring',
              stiffness: 500,
              damping: 30
            }}
          >
            {item.title}
          </motion.li>
        ))}
      </motion.ul>
    </LayoutGroup>
  )
}

// Accordion example
export function Accordion({ title, children }) {
  const [isOpen, setIsOpen] = useState(false)

  return (
    <motion.div layout>
      <motion.button
        layout
        onClick={() => setIsOpen(!isOpen)}
      >
        {title}
      </motion.button>

      <AnimatePresence>
        {isOpen && (
          <motion.div
            initial={{ height: 0, opacity: 0 }}
            animate={{ height: 'auto', opacity: 1 }}
            exit={{ height: 0, opacity: 0 }}
            transition={{ duration: 0.3 }}
          >
            {children}
          </motion.div>
        )}
      </AnimatePresence>
    </motion.div>
  )
}

Motion Properties:

  • layout prop: Auto-animates position/size changes
  • LayoutGroup: Shared layout context for smooth transitions
  • Spring: Natural feel for reordering
  • Auto height: Smooth accordion expand/collapse

Pattern 6: Drag Interaction

Use Case: Sliders, reorderable lists, custom controls

Implementation:

import { motion } from 'framer-motion'

export function DraggableCard() {
  return (
    <motion.div
      drag
      dragConstraints={{
        top: -100,
        left: -100,
        right: 100,
        bottom: 100
      }}
      dragElastic={0.1}
      dragTransition={{
        bounceStiffness: 600,
        bounceDamping: 20
      }}
      whileDrag={{ scale: 1.05, cursor: 'grabbing' }}
      style={{
        width: 200,
        height: 200,
        backgroundColor: '#6366F1',
        borderRadius: 12,
        cursor: 'grab'
      }}
    >
      Drag me!
    </motion.div>
  )
}

// Swipe to dismiss
export function SwipeCard({ onDismiss }) {
  return (
    <motion.div
      drag="x"
      dragConstraints={{ left: 0, right: 0 }}
      onDragEnd={(e, { offset, velocity }) => {
        const swipe = Math.abs(offset.x) * velocity.x

        if (swipe < -10000 || swipe > 10000) {
          onDismiss()
        }
      }}
    >
      Swipe to dismiss
    </motion.div>
  )
}

Motion Properties:

  • drag: Enable dragging (x, y, or both)
  • dragConstraints: Limit drag area
  • dragElastic: Bounce at boundaries
  • whileDrag: Scale up while dragging
  • Swipe detection: velocity × offset

Timing & Easing Guide

Duration Guidelines

Instant (0-100ms):

  • Immediate feedback (button clicks, checkbox toggles)
  • No animation needed

Quick (100-200ms):

  • State changes (hover, focus)
  • Small movements (icon rotations)
  • Use: transition={{ duration: 0.15 }}

Standard (200-400ms):

  • Modals, dropdowns, tooltips
  • Card reveals, fades
  • Use: transition={{ duration: 0.3 }}

Slow (400-600ms):

  • Page transitions
  • Large layout shifts
  • Scroll-triggered reveals
  • Use: transition={{ duration: 0.5 }}

Very Slow (600ms+):

  • Only for intentional dramatic effect
  • Onboarding tours, hero animations
  • Use sparingly: transition={{ duration: 0.8 }}

Easing Functions

Ease Out (Most Common):

transition={{ ease: 'easeOut' }}
// or custom: ease: [0.4, 0, 0.2, 1]
  • Use: Enter animations, reveals, fades
  • Feel: Starts fast, slows down (natural deceleration)

Ease In:

transition={{ ease: 'easeIn' }}
  • Use: Exit animations, dismissals
  • Feel: Starts slow, speeds up (falling away)

Ease In-Out:

transition={{ ease: 'easeInOut' }}
  • Use: Mid-journey animations (A → B → A)
  • Feel: Smooth start and end

Spring (Most Natural):

transition={{
  type: 'spring',
  stiffness: 500,  // Higher = faster
  damping: 30      // Higher = less bounce
}}
  • Use: Interactive elements (buttons, drag)
  • Feel: Physical, bouncy, responsive
  • Stiffness: 300-700 range (500 is good default)
  • Damping: 20-40 range (30 is good default)

Linear:

transition={{ ease: 'linear' }}
  • Use: Looping animations, loading spinners
  • Feel: Mechanical, constant speed

Accessibility & Reduced Motion

Respect User Preferences

Always include reduced motion support:

import { useReducedMotion } from 'framer-motion'

export function AccessibleAnimation({ children }) {
  const shouldReduceMotion = useReducedMotion()

  const variants = {
    hidden: { opacity: 0, y: shouldReduceMotion ? 0 : 20 },
    visible: {
      opacity: 1,
      y: 0,
      transition: {
        duration: shouldReduceMotion ? 0.01 : 0.4
      }
    }
  }

  return (
    <motion.div
      variants={variants}
      initial="hidden"
      animate="visible"
    >
      {children}
    </motion.div>
  )
}

What to Reduce:

  • Disable decorative animations
  • Reduce duration to 0.01s (instant but still works)
  • Keep essential feedback (button press, loading states)
  • Maintain layout shifts (don't disable layout prop)

Performance Optimization

GPU-Accelerated Properties

Animate These (60fps guaranteed):

// ✅ Fast (uses transform)
<motion.div
  animate={{
    x: 100,           // translateX
    y: 100,           // translateY
    scale: 1.5,       // scale
    rotate: 45,       // rotate
    opacity: 0.5      // opacity
  }}
/>

Avoid Animating (causes reflow/repaint):

// ❌ Slow (triggers layout recalculation)
<motion.div
  animate={{
    width: 300,
    height: 200,
    top: 100,
    left: 100,
    margin: 20
  }}
/>

Will-Change Optimization

For complex animations:

<motion.div
  style={{ willChange: 'transform' }}
  animate={{ x: 100, rotate: 45 }}
/>

Warning: Only use for actively animating elements. Remove after animation completes.

Lazy Loading Animations

import { motion, LazyMotion, domAnimation } from 'framer-motion'

// Reduces bundle size by 30KB
export function OptimizedApp() {
  return (
    <LazyMotion features={domAnimation}>
      <motion.div>Optimized animations</motion.div>
    </LazyMotion>
  )
}

Common Animation Patterns

Page Transitions (Next.js)

// app/layout.tsx
import { motion, AnimatePresence } from 'framer-motion'
import { usePathname } from 'next/navigation'

export default function Layout({ children }) {
  const pathname = usePathname()

  return (
    <AnimatePresence mode="wait">
      <motion.div
        key={pathname}
        initial={{ opacity: 0, x: 20 }}
        animate={{ opacity: 1, x: 0 }}
        exit={{ opacity: 0, x: -20 }}
        transition={{ duration: 0.3 }}
      >
        {children}
      </motion.div>
    </AnimatePresence>
  )
}

Loading States

export function LoadingSpinner() {
  return (
    <motion.div
      animate={{ rotate: 360 }}
      transition={{
        duration: 1,
        repeat: Infinity,
        ease: 'linear'
      }}
      style={{
        width: 40,
        height: 40,
        border: '3px solid #E5E7EB',
        borderTopColor: '#6366F1',
        borderRadius: '50%'
      }}
    />
  )
}

Success Checkmark

import { motion } from 'framer-motion'

const checkmarkVariants = {
  hidden: { pathLength: 0, opacity: 0 },
  visible: {
    pathLength: 1,
    opacity: 1,
    transition: {
      pathLength: { duration: 0.5, ease: 'easeOut' },
      opacity: { duration: 0.01 }
    }
  }
}

export function SuccessCheckmark() {
  return (
    <svg width="60" height="60" viewBox="0 0 60 60">
      <motion.path
        d="M15,30 L25,40 L45,20"
        fill="none"
        stroke="#10B981"
        strokeWidth="4"
        strokeLinecap="round"
        variants={checkmarkVariants}
        initial="hidden"
        animate="visible"
      />
    </svg>
  )
}

Motion Design Checklist

When designing animations, verify:

  • Purpose: Does this animation serve a clear UX purpose?
  • Duration: Is it 200-500ms (or justified if longer)?
  • Easing: Using appropriate curve (ease-out for most cases)?
  • Performance: Animating only transform and opacity?
  • Reduced Motion: Respects prefers-reduced-motion?
  • Consistency: Matches timing/style of other animations?
  • Mobile: Tested on low-end devices?
  • Accessibility: Keyboard users can skip/disable?
  • Bundle Size: Using LazyMotion if many animations?

Advanced Patterns

For complex scenarios, see:

  • references/motion-patterns.md - Advanced animation patterns (shared transitions, orchestration)
  • references/gesture-interactions.md - Drag, swipe, pan, pinch gestures
  • references/scroll-effects.md - Parallax, scroll-linked animations, viewport triggers

Output Format

When providing motion design guidance, structure as:

## Animation: [Component Name]

### Purpose
[Why this animation exists - user benefit]

### Motion Specs
- **Trigger**: [Hover, click, scroll, mount]
- **Properties**: [x, y, scale, opacity, etc.]
- **Duration**: [200ms, 400ms, etc.]
- **Easing**: [ease-out, spring, etc.]
- **Delay**: [If staggered or sequenced]

### Implementation
[Framer Motion code]

### Reduced Motion Fallback
[Simplified version for accessibility]

### Performance Notes
[GPU acceleration, potential issues]

Remember: Less is more. Purposeful motion enhances usability. Excessive animation annoys users.

Score

Total Score

40/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon