animation-designer

Smooth, professional web animations using Framer Motion and CSS with production-ready patterns. Covers UI animations (page transitions, component enter/exit, hover effects), scroll animations (parallax, scroll-triggered reveals, progress indicators), and micro-interactions (button feedback, form states, drag and drop) Provides 5+ common animation patterns including fade-in, staggered lists, button hover, modal dialogs, and page transitions with ready-to-use code examples Includes scroll animation techniques with useScroll and useTransform hooks for parallax effects and scroll-progress indicators Offers loading animation patterns (spinners, skeleton loaders, pulsing dots) and CSS keyframe animations with Tailwind integration Emphasizes performance best practices (GPU-accelerated transforms, optimal duration ranges, easing strategies) and accessibility considerations with useReducedMotion support

INSTALLATION
npx skills add https://github.com/daffy0208/ai-dev-standards --skill animation-designer
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

Animation Designer Skill

I help you create smooth, professional animations for web applications using Framer Motion and CSS.

What I Do

UI Animations:

  • Page transitions
  • Component enter/exit animations
  • Hover effects, button interactions
  • Loading animations

Scroll Animations:

  • Parallax effects
  • Scroll-triggered animations
  • Progress indicators

Micro-interactions:

  • Button press feedback
  • Form field focus states
  • Success/error animations
  • Drag and drop feedback

Framer Motion Basics

Installation

npm install framer-motion

Basic Animation

import { motion } from 'framer-motion'

export function FadeIn({ children }: { children: React.ReactNode }) {

  return (

    <motion.div

      initial={{ opacity: 0 }}

      animate={{ opacity: 1 }}

      transition={{ duration: 0.5 }}

    >

      {children}

    </motion.div>

  )

}

Common Animation Patterns

Pattern 1: Fade In on Mount

import { motion } from 'framer-motion'

export function Card({ children }: { children: React.ReactNode }) {

  return (

    <motion.div

      initial={{ opacity: 0, y: 20 }}

      animate={{ opacity: 1, y: 0 }}

      transition={{ duration: 0.4, ease: 'easeOut' }}

      className="p-6 bg-white rounded-lg shadow"

    >

      {children}

    </motion.div>

  )

}

Pattern 2: Staggered List Animation

import { motion } from 'framer-motion'

const container = {

  hidden: { opacity: 0 },

  show: {

    opacity: 1,

    transition: {

      staggerChildren: 0.1

    }

  }

}

const item = {

  hidden: { opacity: 0, y: 20 },

  show: { opacity: 1, y: 0 }

}

export function List({ items }: { items: string[] }) {

  return (

    <motion.ul

      variants={container}

      initial="hidden"

      animate="show"

    >

      {items.map((text, i) => (

        <motion.li key={i} variants={item}>

          {text}

        </motion.li>

      ))}

    </motion.ul>

  )

}

Pattern 3: Button Hover Animation

import { motion } from 'framer-motion'

export function AnimatedButton({ children, onClick }: {

  children: React.ReactNode

  onClick: () => void

}) {

  return (

    <motion.button

      onClick={onClick}

      whileHover={{ scale: 1.05 }}

      whileTap={{ scale: 0.95 }}

      transition={{ type: 'spring', stiffness: 400, damping: 17 }}

      className="px-6 py-3 bg-blue-600 text-white rounded-lg"

    >

      {children}

    </motion.button>

  )

}

Pattern 4: Modal / Dialog Animation

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

export function Modal({ isOpen, onClose, children }: {

  isOpen: boolean

  onClose: () => void

  children: React.ReactNode

}) {

  return (

    <AnimatePresence>

      {isOpen &#x26;&#x26; (

        <>

          {/* Backdrop */}

          <motion.div

            initial={{ opacity: 0 }}

            animate={{ opacity: 1 }}

            exit={{ opacity: 0 }}

            onClick={onClose}

            className="fixed inset-0 bg-black/50 z-40"

          />

          {/* Modal */}

          <motion.div

            initial={{ opacity: 0, scale: 0.9, y: 20 }}

            animate={{ opacity: 1, scale: 1, y: 0 }}

            exit={{ opacity: 0, scale: 0.9, y: 20 }}

            transition={{ type: 'spring', damping: 25, stiffness: 300 }}

            className="fixed inset-0 flex items-center justify-center z-50 p-4"

          >

            <div className="bg-white rounded-lg p-6 max-w-md w-full">

              {children}

            </div>

          </motion.div>

        </>

      )}

    </AnimatePresence>

  )

}

Pattern 5: Page Transition

'use client'

import { motion } from 'framer-motion'

import { usePathname } from 'next/navigation'

export function PageTransition({ children }: { children: React.ReactNode }) {

  const pathname = usePathname()

  return (

    <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>

  )

}

// Usage in layout

export default function Layout({ children }) {

  return (

    <PageTransition>

      {children}

    </PageTransition>

  )

}

Scroll Animations

Scroll-Triggered Animation

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

import { useRef } from 'react'

export function ScrollReveal({ children }: { children: React.ReactNode }) {

  const ref = useRef(null)

  const { scrollYProgress } = useScroll({

    target: ref,

    offset: ['start end', 'end start']

  })

  const opacity = useTransform(scrollYProgress, [0, 0.3, 0.7, 1], [0, 1, 1, 0])

  const y = useTransform(scrollYProgress, [0, 0.3, 0.7, 1], [100, 0, 0, -100])

  return (

    <motion.div

      ref={ref}

      style={{ opacity, y }}

    >

      {children}

    </motion.div>

  )

}

Parallax Effect

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

export function ParallaxSection() {

  const { scrollY } = useScroll()

  const y = useTransform(scrollY, [0, 500], [0, 150])

  return (

    <div className="relative h-screen overflow-hidden">

      <motion.div

        style={{ y }}

        className="absolute inset-0"

      >

        <img src="/background.jpg" alt="" className="w-full h-full object-cover" />

      </motion.div>

      <div className="relative z-10 flex items-center justify-center h-full">

        <h1 className="text-6xl font-bold text-white">

          Parallax Effect

        </h1>

      </div>

    </div>

  )

}

Scroll Progress Indicator

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

export function ScrollProgress() {

  const { scrollYProgress } = useScroll()

  return (

    <motion.div

      style={{ scaleX: scrollYProgress }}

      className="fixed top-0 left-0 right-0 h-1 bg-blue-600 origin-left z-50"

    />

  )

}

Loading Animations

Spinner

import { motion } from 'framer-motion'

export function Spinner() {

  return (

    <motion.div

      animate={{ rotate: 360 }}

      transition={{

        duration: 1,

        repeat: Infinity,

        ease: 'linear'

      }}

      className="w-8 h-8 border-4 border-blue-600 border-t-transparent rounded-full"

    />

  )

}

Skeleton Loader

import { motion } from 'framer-motion'

export function SkeletonLoader() {

  return (

    <motion.div

      animate={{ opacity: [0.5, 1, 0.5] }}

      transition={{

        duration: 1.5,

        repeat: Infinity,

        ease: 'easeInOut'

      }}

      className="bg-gray-200 rounded h-4 w-full"

    />

  )

}

Pulsing Dots

import { motion } from 'framer-motion'

const dotVariants = {

  start: { scale: 0.8, opacity: 0.5 },

  end: { scale: 1.2, opacity: 1 }

}

export function PulsingDots() {

  return (

    <div className="flex gap-2">

      {[0, 1, 2].map((i) => (

        <motion.div

          key={i}

          variants={dotVariants}

          animate="end"

          initial="start"

          transition={{

            duration: 0.6,

            repeat: Infinity,

            repeatType: 'reverse',

            delay: i * 0.2

          }}

          className="w-3 h-3 bg-blue-600 rounded-full"

        />

      ))}

    </div>

  )

}

CSS Animations

Keyframe Animations

/* Fade in animation */

@keyframes fadeIn {

  from {

    opacity: 0;

    transform: translateY(20px);

  }

  to {

    opacity: 1;

    transform: translateY(0);

  }

}

.fade-in {

  animation: fadeIn 0.5s ease-out;

}

/* Slide in from right */

@keyframes slideInRight {

  from {

    transform: translateX(100%);

  }

  to {

    transform: translateX(0);

  }

}

.slide-in-right {

  animation: slideInRight 0.3s ease-out;

}

/* Bounce */

@keyframes bounce {

  0%,

  100% {

    transform: translateY(0);

  }

  50% {

    transform: translateY(-10px);

  }

}

.bounce {

  animation: bounce 0.5s ease-in-out infinite;

}

Tailwind Animations

// tailwind.config.js

module.exports = {

  theme: {

    extend: {

      keyframes: {

        'fade-in': {

          '0%': { opacity: '0', transform: 'translateY(10px)' },

          '100%': { opacity: '1', transform: 'translateY(0)' }

        },

        'slide-in': {

          '0%': { transform: 'translateX(-100%)' },

          '100%': { transform: 'translateX(0)' }

        }

      },

      animation: {

        'fade-in': 'fade-in 0.5s ease-out',

        'slide-in': 'slide-in 0.3s ease-out'

      }

    }

  }

}

Usage:

<div className="animate-fade-in">Fades in</div>

<div className="animate-slide-in">Slides in</div>

Micro-Interactions

Success Checkmark Animation

import { motion } from 'framer-motion'

export function SuccessCheckmark() {

  return (

    <motion.svg

      width="48"

      height="48"

      viewBox="0 0 48 48"

      initial={{ scale: 0 }}

      animate={{ scale: 1 }}

      transition={{ type: 'spring', stiffness: 300, damping: 20 }}

    >

      <motion.circle

        cx="24"

        cy="24"

        r="22"

        fill="none"

        stroke="#10B981"

        strokeWidth="4"

        initial={{ pathLength: 0 }}

        animate={{ pathLength: 1 }}

        transition={{ duration: 0.5 }}

      />

      <motion.path

        d="M12 24 L20 32 L36 16"

        fill="none"

        stroke="#10B981"

        strokeWidth="4"

        strokeLinecap="round"

        strokeLinejoin="round"

        initial={{ pathLength: 0 }}

        animate={{ pathLength: 1 }}

        transition={{ duration: 0.3, delay: 0.3 }}

      />

    </motion.svg>

  )

}

Notification Badge

import { motion } from 'framer-motion'

export function NotificationBadge({ count }: { count: number }) {

  return (

    <div className="relative">

      <button className="p-2">

        <BellIcon />

      </button>

      {count > 0 &#x26;&#x26; (

        <motion.div

          initial={{ scale: 0 }}

          animate={{ scale: 1 }}

          transition={{ type: 'spring', stiffness: 500, damping: 15 }}

          className="absolute -top-1 -right-1 bg-red-600 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center"

        >

          {count}

        </motion.div>

      )}

    </div>

  )

}

Animation Best Practices

1. Performance

// ✅ Good: Animate transform and opacity (GPU accelerated)

<motion.div

  animate={{ x: 100, opacity: 0.5 }}

/>

// ❌ Bad: Animate width, height (triggers layout)

<motion.div

  animate={{ width: '100%', height: '200px' }}

/>

2. Duration

// Too fast: < 100ms (feels abrupt)

// Too slow: > 500ms (feels sluggish)

// ✅ Sweet spot: 200-400ms for most UI animations

<motion.div

  animate={{ opacity: 1 }}

  transition={{ duration: 0.3 }}

/>

3. Easing

// Natural motion: easeOut (starts fast, ends slow)

<motion.div

  animate={{ y: 0 }}

  transition={{ ease: 'easeOut' }}

/>

// Bouncy: spring

<motion.button

  whileTap={{ scale: 0.95 }}

  transition={{ type: 'spring', stiffness: 400 }}

/>

4. Reduce Motion (Accessibility)

import { useReducedMotion } from 'framer-motion'

export function AccessibleAnimation({ children }: { children: React.ReactNode }) {

  const shouldReduceMotion = useReducedMotion()

  return (

    <motion.div

      initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 20 }}

      animate={{ opacity: 1, y: 0 }}

      transition={{

        duration: shouldReduceMotion ? 0 : 0.4

      }}

    >

      {children}

    </motion.div>

  )

}

Complex Animations

Drag and Drop

import { motion } from 'framer-motion'

import { useState } from 'react'

export function Draggable() {

  const [position, setPosition] = useState({ x: 0, y: 0 })

  return (

    <motion.div

      drag

      dragConstraints={{ left: 0, right: 300, top: 0, bottom: 300 }}

      dragElastic={0.1}

      onDragEnd={(e, info) => {

        setPosition({ x: info.point.x, y: info.point.y })

      }}

      className="w-24 h-24 bg-blue-600 rounded-lg cursor-grab active:cursor-grabbing"

    />

  )

}

Animated Number Counter

import { motion, useSpring, useTransform } from 'framer-motion'

import { useEffect } from 'react'

export function AnimatedNumber({ value }: { value: number }) {

  const spring = useSpring(0, { stiffness: 100, damping: 30 })

  const display = useTransform(spring, (current) =>

    Math.round(current).toLocaleString()

  )

  useEffect(() => {

    spring.set(value)

  }, [spring, value])

  return <motion.span>{display}</motion.span>

}

// Usage

<AnimatedNumber value={1250} />

When to Use Me

Perfect for:

  • Creating polished UI animations
  • Building interactive components
  • Adding scroll effects
  • Designing loading states
  • Improving user feedback

I'll help you:

  • Choose the right animation type
  • Implement smooth transitions
  • Optimize animation performance
  • Ensure accessibility
  • Create delightful micro-interactions

What I'll Create

✨ Page Transitions

🎯 Micro-Interactions

📜 Scroll Animations

⏳ Loading States

🎨 Hover Effects

🎪 Complex Animations

Let's make your interfaces feel alive!

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card