Skip to content
Back to skills

Framer Motion Patterns

ASecurity

Comprehensive Framer Motion patterns — layout animations, gestures, scroll-triggered, stagger, shared layout transitions.

  • 6 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added June 1, 2026
ai-agentsgoreactnextjsnodespringperformance

Works with

  • cli

Security analysis

A100/100

Scanned June 1, 2026

npx -y skills add KevinZai/commander --skill framer-motion-patterns --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Framer Motion Patterns?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Framer Motion Patterns
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/kevinzai-framer-motion-patterns/badge)](https://www.skillsdirectory.com/skills/kevinzai-framer-motion-patterns)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: Framer Motion Patterns
description: "Comprehensive Framer Motion patterns — layout animations, gestures, scroll-triggered, stagger, shared layout transitions."
version: 1.0.0
category: animation
parent: ccc-design
---

# Framer Motion Patterns

Deep patterns for Framer Motion in React. Goes beyond basics into layout animations, scroll-driven effects, gesture recognition, and orchestrated sequences.

## Core Principles

- Every animation needs a reason (entrance, feedback, spatial continuity, hierarchy)
- Always respect `prefers-reduced-motion` via `useReducedMotion()`
- Prefer `layout` prop over manual position animations
- Use `will-change: transform` sparingly — let Framer handle optimization
- Spring physics over duration-based by default

## Pattern Library

### 1. Staggered List Entrance

```tsx
const container = {
  hidden: { opacity: 0 },
  show: {
    opacity: 1,
    transition: { staggerChildren: 0.08, delayChildren: 0.1 }
  }
};

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

function StaggeredList({ items }: { items: Item[] }) {
  return (
    <motion.ul variants={container} initial="hidden" animate="show">
      {items.map((item) => (
        <motion.li key={item.id} variants={item}>
          {item.content}
        </motion.li>
      ))}
    </motion.ul>
  );
}
```

### 2. Shared Layout Transitions

```tsx
function TabPanel({ tabs, activeTab, onTabChange }: TabPanelProps) {
  return (
    <div className="tab-container">
      {tabs.map((tab) => (
        <button key={tab.id} onClick={() => onTabChange(tab.id)}>
          {tab.label}
          {activeTab === tab.id && (
            <motion.div
              className="active-indicator"
              layoutId="active-tab"
              transition={{ type: "spring", stiffness: 500, damping: 30 }}
            />
          )}
        </button>
      ))}
    </div>
  );
}
```

### 3. AnimatePresence for Enter/Exit

```tsx
function Notification({ message, isVisible, onDismiss }: NotificationProps) {
  return (
    <AnimatePresence mode="wait">
      {isVisible && (
        <motion.div
          key="notification"
          initial={{ opacity: 0, y: -20, scale: 0.95 }}
          animate={{ opacity: 1, y: 0, scale: 1 }}
          exit={{ opacity: 0, y: -20, scale: 0.95 }}
          transition={{ type: "spring", stiffness: 400, damping: 25 }}
        >
          {message}
          <button onClick={onDismiss}>Dismiss</button>
        </motion.div>
      )}
    </AnimatePresence>
  );
}
```

### 4. Scroll-Triggered Reveal

```tsx
function ScrollReveal({ 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: 40 }}
      animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 40 }}
      transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
    >
      {children}
    </motion.div>
  );
}
```

### 5. Scroll-Linked Progress

```tsx
function ScrollProgress() {
  const { scrollYProgress } = useScroll();
  const scaleX = useSpring(scrollYProgress, { stiffness: 100, damping: 30 });

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

### 6. Gesture-Driven Interactions

```tsx
function DraggableCard({ children }: { children: React.ReactNode }) {
  const x = useMotionValue(0);
  const rotateZ = useTransform(x, [-200, 200], [-15, 15]);
  const opacity = useTransform(x, [-200, -100, 0, 100, 200], [0.5, 1, 1, 1, 0.5]);

  return (
    <motion.div
      drag="x"
      dragConstraints={{ left: 0, right: 0 }}
      dragElastic={0.7}
      style={{ x, rotateZ, opacity }}
      whileTap={{ scale: 0.98 }}
      onDragEnd={(_, info) => {
        if (Math.abs(info.offset.x) > 150) {
          // Trigger swipe action
        }
      }}
    >
      {children}
    </motion.div>
  );
}
```

### 7. Layout Animation (Reorder)

```tsx
function ReorderableList({ items, onReorder }: ReorderableListProps) {
  return (
    <Reorder.Group axis="y" values={items} onReorder={onReorder}>
      {items.map((item) => (
        <Reorder.Item
          key={item.id}
          value={item}
          whileDrag={{ scale: 1.02, boxShadow: "0 8px 24px rgba(0,0,0,0.15)" }}
          transition={{ type: "spring", stiffness: 400, damping: 25 }}
        >
          {item.content}
        </Reorder.Item>
      ))}
    </Reorder.Group>
  );
}
```

### 8. Page Transitions (Next.js App Router)

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

function Template({ children }: { children: React.ReactNode }) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 8 }}
      animate={{ opacity: 1, y: 0 }}
      exit={{ opacity: 0, y: -8 }}
      transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
    >
      {children}
    </motion.div>
  );
}

export default Template;
```

## Spring Presets

| Name | stiffness | damping | Use Case |
|------|-----------|---------|----------|
| Snappy | 500 | 30 | UI toggles, tabs, small elements |
| Smooth | 300 | 24 | Cards, panels, medium elements |
| Gentle | 200 | 20 | Page transitions, large elements |
| Bouncy | 600 | 12 | Playful UI, notifications, badges |
| Heavy | 150 | 30 | Modals, drawers, overlays |

## Performance Guidelines

- Use `layout` prop only on elements that actually change position
- Avoid animating `width`/`height` — use `scale` transforms instead
- Set `layout="position"` when only position changes (not size)
- Use `layoutId` for cross-component shared transitions
- Wrap expensive children in `motion.div` with `layout={false}` to opt out
- Use `useMotionValueEvent` over `useEffect` for motion value changes

## Accessibility

```tsx
function AccessibleAnimation({ children }: { children: React.ReactNode }) {
  const prefersReducedMotion = useReducedMotion();

  return (
    <motion.div
      initial={prefersReducedMotion ? false : { opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={prefersReducedMotion
        ? { duration: 0 }
        : { type: "spring", stiffness: 300, damping: 24 }
      }
    >
      {children}
    </motion.div>
  );
}
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.