Claude
Skills
Sign in
Back

motion

Included with Lifetime
$97 forever

Setup Motion (Framer Motion) for animations and transitions in Next.js. Use this skill when the user says "setup motion", "add animations", "framer motion", "animated transitions", "setup framer", or "motion effects".

Web Dev

What this skill does


# Motion Skill

Production-grade animation library for React 19 and Next.js App Router. Motion (formerly Framer Motion) provides declarative animations, gestures, scroll effects, and layout transitions.

## Installation

```bash
bun add motion
```

## Quick Start

Motion components require client-side rendering in Next.js App Router.

```tsx
// components/motion/animated-button.tsx
"use client";

import { motion } from "motion/react";

export function AnimatedButton({ children }: { children: React.ReactNode }) {
  return (
    <motion.button
      whileHover={{ scale: 1.05 }}
      whileTap={{ scale: 0.95 }}
      transition={{ type: "spring", stiffness: 400, damping: 17 }}
      className="px-6 py-3 bg-primary text-primary-foreground rounded-lg"
    >
      {children}
    </motion.button>
  );
}
```

## Core Concepts

### Animation Anatomy

Every Motion animation has three parts:

1. **Initial State** - Where the animation starts (`initial`)
2. **Target State** - Where the animation ends (`animate`)
3. **Transition** - How it moves between states (`transition`)

```tsx
<motion.div
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ duration: 0.3, ease: "easeOut" }}
/>
```

### Spring vs Tween

- **Spring**: Physics-based, natural feel (use for `scale`, `x`, `y`, `rotate`)
- **Tween**: Duration-based, precise timing (use for `opacity`, `color`)

```tsx
// Spring (bouncy, responsive)
transition={{ type: "spring", stiffness: 400, damping: 17 }}

// Tween (precise timing)
transition={{ type: "tween", duration: 0.3, ease: "easeOut" }}
```

### Variants (Declarative Animation States)

Organize complex animations with named states:

```tsx
const cardVariants = {
  hidden: { opacity: 0, y: 20 },
  visible: { opacity: 1, y: 0 },
  hover: { scale: 1.02 }
};

<motion.div
  variants={cardVariants}
  initial="hidden"
  animate="visible"
  whileHover="hover"
/>
```

---

## Utility Components

### 1. Motion Reveal (Scroll-Triggered)

```tsx
// components/motion/motion-reveal.tsx
"use client";

import { motion, type Variant } from "motion/react";
import type { ReactNode } from "react";

type RevealDirection = "up" | "down" | "left" | "right" | "none";

type MotionRevealProps = {
  children: ReactNode;
  direction?: RevealDirection;
  delay?: number;
  duration?: number;
  once?: boolean;
  amount?: number;
  className?: string;
};

const directionVariants: Record<RevealDirection, { hidden: Variant; visible: Variant }> = {
  up: {
    hidden: { opacity: 0, y: 50 },
    visible: { opacity: 1, y: 0 }
  },
  down: {
    hidden: { opacity: 0, y: -50 },
    visible: { opacity: 1, y: 0 }
  },
  left: {
    hidden: { opacity: 0, x: 50 },
    visible: { opacity: 1, x: 0 }
  },
  right: {
    hidden: { opacity: 0, x: -50 },
    visible: { opacity: 1, x: 0 }
  },
  none: {
    hidden: { opacity: 0 },
    visible: { opacity: 1 }
  }
};

export function MotionReveal({
  children,
  direction = "up",
  delay = 0,
  duration = 0.5,
  once = true,
  amount = 0.3,
  className
}: MotionRevealProps) {
  const variants = directionVariants[direction];

  return (
    <motion.div
      initial="hidden"
      whileInView="visible"
      viewport={{ once, amount }}
      variants={{
        hidden: variants.hidden,
        visible: {
          ...variants.visible,
          transition: {
            type: "spring",
            stiffness: 100,
            damping: 15,
            delay
          }
        }
      }}
      className={className}
    >
      {children}
    </motion.div>
  );
}
```

**Usage:**
```tsx
<MotionReveal direction="up" delay={0.1}>
  <Card>Content reveals on scroll</Card>
</MotionReveal>
```

### 2. Motion Stagger (List Animation)

```tsx
// components/motion/motion-stagger.tsx
"use client";

import { motion } from "motion/react";
import type { ReactNode } from "react";

type MotionStaggerProps = {
  children: ReactNode;
  staggerDelay?: number;
  delayChildren?: number;
  className?: string;
};

const containerVariants = {
  hidden: { opacity: 0 },
  visible: (custom: { staggerDelay: number; delayChildren: number }) => ({
    opacity: 1,
    transition: {
      staggerChildren: custom.staggerDelay,
      delayChildren: custom.delayChildren
    }
  })
};

export function MotionStagger({
  children,
  staggerDelay = 0.1,
  delayChildren = 0.2,
  className
}: MotionStaggerProps) {
  return (
    <motion.div
      variants={containerVariants}
      initial="hidden"
      animate="visible"
      custom={{ staggerDelay, delayChildren }}
      className={className}
    >
      {children}
    </motion.div>
  );
}

// Child component for stagger items
type MotionStaggerItemProps = {
  children: ReactNode;
  className?: string;
};

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

export function MotionStaggerItem({ children, className }: MotionStaggerItemProps) {
  return (
    <motion.div variants={itemVariants} className={className}>
      {children}
    </motion.div>
  );
}
```

**Usage:**
```tsx
<MotionStagger staggerDelay={0.1}>
  {items.map((item) => (
    <MotionStaggerItem key={item.id}>
      <Card>{item.title}</Card>
    </MotionStaggerItem>
  ))}
</MotionStagger>
```

### 3. Motion Button (Delightful Microinteractions)

```tsx
// components/motion/motion-button.tsx
"use client";

import { motion, type HTMLMotionProps } from "motion/react";
import type { ReactNode } from "react";

type ButtonVariant = "snappy" | "bouncy" | "subtle" | "heavy";

type MotionButtonProps = HTMLMotionProps<"button"> & {
  children: ReactNode;
  variant?: ButtonVariant;
};

const springConfigs: Record<ButtonVariant, { stiffness: number; damping: number; mass?: number }> = {
  snappy: { stiffness: 400, damping: 17 },
  bouncy: { stiffness: 300, damping: 10 },
  subtle: { stiffness: 500, damping: 30 },
  heavy: { stiffness: 230, damping: 4, mass: 4 }
};

const scaleConfigs: Record<ButtonVariant, { hover: number; tap: number }> = {
  snappy: { hover: 1.05, tap: 0.95 },
  bouncy: { hover: 1.1, tap: 0.9 },
  subtle: { hover: 1.02, tap: 0.98 },
  heavy: { hover: 1.05, tap: 0.95 }
};

export function MotionButton({
  children,
  variant = "snappy",
  className,
  ...props
}: MotionButtonProps) {
  const spring = springConfigs[variant];
  const scale = scaleConfigs[variant];

  return (
    <motion.button
      whileHover={{ scale: scale.hover }}
      whileTap={{ scale: scale.tap }}
      transition={{ type: "spring", ...spring }}
      className={className}
      {...props}
    >
      {children}
    </motion.button>
  );
}
```

**Usage:**
```tsx
<MotionButton variant="snappy" className="px-6 py-3 bg-primary text-white rounded-lg">
  Click Me
</MotionButton>
```

### 4. Motion Presence (Enter/Exit Animations)

```tsx
// components/motion/motion-presence.tsx
"use client";

import { AnimatePresence, motion } from "motion/react";
import type { ReactNode } from "react";

type PresenceVariant = "fade" | "slide" | "scale" | "slideUp";

type MotionPresenceProps = {
  children: ReactNode;
  isVisible: boolean;
  variant?: PresenceVariant;
  className?: string;
};

const presenceVariants = {
  fade: {
    initial: { opacity: 0 },
    animate: { opacity: 1 },
    exit: { opacity: 0 }
  },
  slide: {
    initial: { opacity: 0, x: 100 },
    animate: { opacity: 1, x: 0 },
    exit: { opacity: 0, x: -100 }
  },
  scale: {
    initial: { opacity: 0, scale: 0.9 },
    animate: { opacity: 1, scale: 1 },
    exit: { opacity: 0, scale: 0.9 }
  },
  slideUp: {
    initial: { opacity: 0, y: 20 },
    animate: { opacity: 1, y: 0 },
    exit: { opacity: 0, y: -20 }
  }
} as const;

export function MotionPresence({
  children,
  isVisible,
  variant = "fade",
  className
}: MotionPresenceProps) {
  const variants = presenceVariants[variant];

  return (
    <AnimatePresence mode="wait">
      {isVisible && (
        <motion.div
         
Files: 3
Size: 52.7 KB
Complexity: 49/100
Category: Web Dev

Related in Web Dev