Skip to main content
skill CX Codex

framer-motion-animator — Claude Code skill: setup and guide

Generates smooth Framer Motion animations and micro-interactions in React. Useful for building page transitions, drag gestures, and scroll effects.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add patricio0312rev/skills --skill framer-motion-animator -a claude-code
Open on GitHub

What framer-motion-animator does

The skill guides coding agents through Framer Motion patterns for React components. It follows a six-step workflow: identifying animation needs, selecting variant types, defining motion properties, tuning spring physics, orchestrating child delays, and keeping transforms on the GPU.

Included code patterns cover basic mounts with FadeIn, exit animations via AnimatePresence, staggered lists, and shared tab indicators with layoutId. It also includes drag physics with dragConstraints, swipe-to-delete gestures, and viewport-triggered entries via useInView.

Your project needs npm install framer-motion installed beforehand. In Next.js App Router setups, page transitions rely on wrapping layouts inside template.tsx.

What it helps with

  • animate modal entrances and exits using AnimatePresence
  • orchestrate staggered list item reveals with staggerChildren
  • create shared tab indicators using LayoutGroup and layoutId
  • implement swipe-to-delete gestures with boundary constraints

Example prompts for the agent

  • add animation to this modal dialog using framer motion
  • create a page transition for Next.js App Router with framer-motion
  • animate component on scroll when it enters the viewport

How to install framer-motion-animator

1Skills CLI (Vercel)

npx skills add patricio0312rev/skills --skill framer-motion-animator -a claude-code

2Manually: put the files in place

~/.claude/skills/framer-motion-animator/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/framer-motion-animator/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about framer-motion-animator

How to install framer-motion-animator in Claude Code?

Run npx skills add patricio0312rev/skills --skill framer-motion-animator -a claude-code in your terminal. You can also save the skill file manually to ~/.claude/skills/framer-motion-animator/SKILL.md.

What does framer-motion-animator do?

It teaches the agent how to build declarative React animations using Framer Motion, covering gestures, stagger sequences, and page transitions.

What dependencies does the generated animation code require?

The project requires the framer-motion package, installed by running npm install framer-motion.

Source text

SKILL.md · MIT
---
name: framer-motion-animator
description: Creates smooth animations and micro-interactions using Framer Motion including page transitions, gestures, scroll-based animations, and orchestrated sequences. Use when users request "add animation", "framer motion", "page transition", "animate component", or "micro-interactions".
---

# Framer Motion Animator

Build delightful animations and interactions with Framer Motion's declarative API.

## Core Workflow

1. **Identify animation needs**: Entrance, exit, hover, gestures
2. **Choose animation type**: Simple, variants, gestures, layout
3. **Define motion values**: Opacity, scale, position, rotation
4. **Add transitions**: Duration, easing, spring physics
5. **Orchestrate sequences**: Stagger, delay, parent-child
6. **Optimize performance**: GPU-accelerated properties

## Installation

```bash
npm install framer-motion
```

## Basic Animations

### Simple Animation

```tsx
import { motion } from 'framer-motion';

// Animate on mount
export function FadeIn({ children }: { children: React.ReactNode }) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.5 }}
    >
      {children}
    </motion.div>
  );
}

// Animate on hover
export function ScaleOnHover({ children }: { children: React.ReactNode }) {
  return (
    <motion.div
      whileHover={{ scale: 1.05 }}
      whileTap={{ scale: 0.95 }}
      transition={{ type: 'spring', stiffness: 400, damping: 17 }}
    >
      {children}
    </motion.div>
  );
}
```

### Exit Animations with AnimatePresence

```tsx
import { motion, AnimatePresence } from 'framer-motion';

export function Modal({ isOpen, onClose, children }: ModalProps) {
  return (
    <AnimatePresence>
      {isOpen && (
        <>
          {/* 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.95, y: 20 }}
            animate={{ opacity: 1, scale: 1, y: 0 }}
            exit={{ opacity: 0, scale: 0.95, y: 20 }}
            transition={{ type: 'spring', damping: 25, stiffness: 300 }}
            className="fixed inset-0 z-50 flex items-center justify-center"
          >
            <div className="bg-white rounded-xl p-6 max-w-md w-full">
              {children}
            </div>
          </motion.div>
        </>
      )}
    </AnimatePresence>
  );
}
```

## Variants Pattern

### Staggered Children

```tsx
const containerVariants = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: {
      staggerChildren: 0.1,
      delayChildren: 0.2,
    },
  },
};

const itemVariants = {
  hidden: { opacity: 0, y: 20 },
  visible: {
    opacity: 1,
    y: 0,
    transition: { type: 'spring', stiffness: 300, damping: 24 },
  },
};

export function StaggeredList({ items }: { items: string[] }) {
  return (
    <motion.ul
      variants={containerVariants}
      initial="hidden"
      animate="visible"
    >
      {items.map((item, index) => (
        <motion.li key={index} variants={itemVariants}>
          {item}
        </motion.li>
      ))}
    </motion.ul>
  );
}
```

### Interactive Variants

```tsx
const buttonVariants = {
  initial: { scale: 1 },
  hover: { scale: 1.05 },
  tap: { scale: 0.95 },
  disabled: { opacity: 0.5, scale: 1 },
};

export function AnimatedButton({
  children,
  disabled,
  onClick,
}: ButtonProps) {
  return (
    <motion.button
      variants={buttonVariants}
      initial="initial"
      whileHover={disabled ? 'disabled' : 'hover'}
      whileTap={disabled ? 'disabled' : 'tap'}
      animate={disabled ? 'disabled' : 'initial'}
      onClick={onClick}
      disabled={disabled}
      className="px-4 py-2 bg-blue-500 text-white rounded-lg"
    >
      {children}
    </motion.button>
  );
}
```

## Page Transitions

### Next.js App Router

```tsx
// app/template.tsx
'use client';

import { motion } from 'framer-motion';

export default function Template({ children }: { children: React.ReactNode }) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      exit={{ opacity: 0, y: 20 }}
      transition={{ duration: 0.3 }}
    >
      {children}
    </motion.div>
  );
}
```

### Shared Layout Animations

```tsx
import { motion, LayoutGroup } from 'framer-motion';

export function Tabs({ tabs, activeTab, onTabChange }: TabsProps) {
  return (
    <LayoutGroup>
      <div className="flex gap-2">
        {tabs.map((tab) => (
          <button
            key={tab.id}
            onClick={() => onTabChange(tab.id)}
            className="relative px-4 py-2"
          >
            {activeTab === tab.id && (
              <motion.div
                layoutId="activeTab"
                className="absolute inset-0 bg-blue-500 rounded-lg"
                transition={{ type: 'spring', stiffness: 500, damping: 30 }}
              />
            )}
            <span className="relative z-10">{tab.label}</span>
          </button>
        ))}
      </div>
    </LayoutGroup>
  );
}
```

## Gesture Animations

### Drag

```tsx
export function DraggableCard() {
  return (
    <motion.div
      drag
      dragConstraints={{ left: -100, right: 100, top: -100, bottom: 100 }}
      dragElastic={0.2}
      dragTransition={{ bounceStiffness: 600, bounceDamping: 20 }}
      whileDrag={{ scale: 1.1, cursor: 'grabbing' }}
      className="w-32 h-32 bg-blue-500 rounded-lg cursor-grab"
    />
  );
}
```

### Swipe to Dismiss

```tsx
export function SwipeToDelete({ onDelete, children }: SwipeProps) {
  return (
    <motion.div
      drag="x"
      dragConstraints={{ left: 0, right: 0 }}
      onDragEnd={(_, info) => {
        if (info.offset.x < -100) {
          onDelete();
        }
      }}
      className="relative"
    >
      {children}
      <motion.div
        className="absolute right-0 inset-y-0 bg-red-500 flex items-center px-4"
        style={{ opacity: 0 }}
        animate={{ opacity: 1 }}
      >
        Delete
      </motion.div>
    </motion.div>
  );
}
```

## Scroll Animations

### Scroll-Triggered

```tsx
import { motion, useInView } from 'framer-motion';
import { useRef } from 'react';

export function FadeInWhenVisible({ children }: { children: React.ReactNode }) {
  const ref = useRef(null);
  const isInView = useInView(ref, { once: true, margin: '-100px' });

  return (
    <motion.div
      ref={ref}
      initial={{ opacity: 0, y: 50 }}
      animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 50 }}
      transition={{ duration: 0.6, ease: 'easeOut' }}
    >
      {children}
    </motion.div>
  );
}
```

### Scroll Progress

```tsx
import { motion, useScroll, useTransform } from 'framer-motion';

export function ParallaxHero() {
  const { scrollY } = useScroll();
  const y = useTransform(scrollY, [0, 500], [0, 150]);
  const opacity = useTransform(scrollY, [0, 300], [1, 0]);

  return (
    <motion.div
      style={{ y, opacity }}
      className="h-screen flex items-center justify-center"
    >
      <h1 className="text-6xl font-bold">Parallax Hero</h1>
    </motion.div>
  );
}

export function ScrollProgress() {
  const { scrollYProgress } = useScroll();

  return (
    <motion.div
      style={{ scaleX: scrollYProgress }}
      className="fixed top-0 left-0 right-0 h-1 bg-blue-500 origin-left z-50"
    />
  );
}
```

## Animation Hooks

### useAnimate (Imperative)

```tsx
import { useAnimate } from 'framer-motion';

export function SubmitButton() {
  const [scope, animate] = useAnimate();

  const handleClick = async () => {
    // Sequence of animations
    await animate(scope.current, { scale: 0.95 }, { duration: 0.1 });
    await animate(scope.current, { scale: 1 }, { type: 'spring' });

    // Success animation
    await animate(
      scope.current,
      { backgroundColor: '#22c55e' },
      { duration: 0.2 }
    );
  };

  return (
    <motion.button ref={scope} onClick={handleClick} className="px-4 py-2">
      Submit
    </motion.button>
  );
}
```

### useMotionValue & useTransform

```tsx
import { motion, useMotionValue, useTransform } from 'framer-motion';

export function RotatingCard() {
  const x = useMotionValue(0);
  const rotateY = useTransform(x, [-200, 200], [-45, 45]);
  const opacity = useTransform(x, [-200, 0, 200], [0.5, 1, 0.5]);

  return (
    <motion.div
      drag="x"
      dragConstraints={{ left: -200, right: 200 }}
      style={{ x, rotateY, opacity }}
      className="w-64 h-96 bg-gradient-to-br from-purple-500 to-pink-500 rounded-xl"
    />
  );
}
```

## Reusable Animation Components

### AnimatedContainer

```tsx
// components/AnimatedContainer.tsx
import { motion, Variants } from 'framer-motion';

const animations: Record<string, Variants> = {
  fadeIn: {
    hidden: { opacity: 0 },
    visible: { opacity: 1 },
  },
  fadeInUp: {
    hidden: { opacity: 0, y: 20 },
    visible: { opacity: 1, y: 0 },
  },
  fadeInDown: {
    hidden: { opacity: 0, y: -20 },
    visible: { opacity: 1, y: 0 },
  },
  scaleIn: {
    hidden: { opacity: 0, scale: 0.8 },
    visible: { opacity: 1, scale: 1 },
  },
  slideInLeft: {
    hidden: { opacity: 0, x: -50 },
    visible: { opacity: 1, x: 0 },
  },
  slideInRight: {
    hidden: { opacity: 0, x: 50 },
    visible: { opacity: 1, x: 0 },
  },
};

interface AnimatedContainerProps {
  children: React.ReactNode;
  animation?: keyof typeof animations;
  delay?: number;
  duration?: number;
  className?: string;
}

export function AnimatedContainer({
  children,
  animation = 'fadeInUp',
  delay = 0,
  duration = 0.5,
  className,
}: AnimatedContainerProps) {
  return (
    <motion.div
      variants={animations[animation]}
      initial="hidden"
      animate="visible"
      transition={{ duration, delay, ease: 'easeOut' }}
      className={className}
    >
      {children}
    </motion.div>
  );
}
```

### AnimatedList

```tsx
// components/AnimatedList.tsx
import { motion } from 'framer-motion';

const containerVariants = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: {
      staggerChildren: 0.05,
    },
  },
};

const itemVariants = {
  hidden: { opacity: 0, x: -20 },
  visible: { opacity: 1, x: 0 },
};

interface AnimatedListProps<T> {
  items: T[];
  renderItem: (item: T, index: number) => React.ReactNode;
  keyExtractor: (item: T, index: number) => string;
  className?: string;
}

export function AnimatedList<T>({
  items,
  renderItem,
  keyExtractor,
  className,
}: AnimatedListProps<T>) {
  return (
    <motion.ul
      variants={containerVariants}
      initial="hidden"
      animate="visible"
      className={className}
    >
      {items.map((item, index) => (
        <motion.li key={keyExtractor(item, index)} variants={itemVariants}>
          {renderItem(item, index)}
        </motion.li>
      ))}
    </motion.ul>
  );
}
```

## Transition Presets

```tsx
// lib/transitions.ts
export const transitions = {
  spring: {
    type: 'spring',
    stiffness: 300,
    damping: 24,
  },
  springBouncy: {
    type: 'spring',
    stiffness: 500,
    damping: 15,
  },
  springStiff: {
    type: 'spring',
    stiffness: 700,
    damping: 30,
  },
  smooth: {
    type: 'tween',
    duration: 0.3,
    ease: 'easeInOut',
  },
  snappy: {
    type: 'tween',
    duration: 0.15,
    ease: [0.25, 0.1, 0.25, 1],
  },
} as const;

// Usage
<motion.div transition={transitions.spring} />
```

## Reduced Motion Support

```tsx
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.5 }}
    >
      {children}
    </motion.div>
  );
}
```

## Best Practices

1. **Use GPU-accelerated properties**: `opacity`, `transform` (not `width`, `height`)
2. **Add `layout` for smooth resizing**: Automatic layout animations
3. **Use `AnimatePresence`**: For exit animations
4. **Prefer springs**: More natural than tween for UI
5. **Respect reduced motion**: Use `useReducedMotion` hook
6. **Avoid animating layout thrashing**: Don't animate `top`, `left`, `width`
7. **Use `layoutId`**: For shared element transitions
8. **Stagger children**: For list animations

## Output Checklist

Every animation implementation should include:

- [ ] Appropriate animation type (simple, variants, gestures)
- [ ] Smooth transitions with proper easing
- [ ] Exit animations with AnimatePresence
- [ ] Reduced motion support
- [ ] GPU-accelerated properties only
- [ ] Spring physics for natural feel
- [ ] Staggered children for lists
- [ ] Performance tested on low-end devices

© patricio0312rev · MIT · patricio0312rev/skills

Similar skills

Everything for Frontend and UI →

From the same set26

eslint-prettier-config Sets up ESLint and Prettier in a TypeScript and React project: configs, rules, scripts and a pre-commit hook. CX Codex +1 1,100installs changelog-writer Turns git commits, PR titles and issue references into a CHANGELOG and release notes. Useful right before a release, when changes need sorting and a version number. CX Codex +1 615installs bruno-collection-generator This skill scans Express, Next.js or Fastify routes and turns them into a Bruno collection: one .bru file per request, with environments and auth. CX Codex +1 603installs jsdoc-typescript-docs A skill that writes JSDoc comments across a TypeScript codebase and builds docs from them with TypeDoc. Handy when functions have piled up without descriptions. CX Codex +1 547installs dockerfile-optimizer A skill that rewrites your Dockerfile around multi-stage builds: smaller images, faster builds, working layer cache. Useful when the image has bloated or CI drags. CX Codex +1 530installs animation-micro-interaction-pack A set of reusable motion patterns for interfaces: hover effects, entrances and exits, gesture feedback. Useful when a UI feels static and you want it to move without hand-tuning timings. CX Codex +1 499installs explaining-code Walks through unfamiliar code in steps: an everyday analogy, an ASCII diagram of the flow, then a line-by-line breakdown. Handy when you inherit a repo. CX Codex +1 473installs conventional-commits The skill writes commit messages to the Conventional Commits spec: type, scope, body and footers. Useful when you want automated versioning and changelogs. CX Codex +1 469installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor