Communitygithub.com

patrones-de-animacion

Patrones de animación listos para producción en React / Next.js: botones, modales, notificaciones toast, listas escalonadas, transiciones de página, animaciones de entrada/salida, scroll y layout, construidos sobre tokens y springs de motion-foundations. Úsalo al animar un elemento UI específico en React o Next.js.

¿Qué es patrones-de-animacion?

Este skill proporciona una colección de patrones de animación listos para copiar y pegar, diseñados para los casos de uso más comunes en React y Next.js. Todos los patrones se basan en los tokens y springs de la librería `motion-foundations`, lo que garantiza consistencia en duraciones, easing y escalas, y evita la definición de valores numéricos en línea. El skill cubre animaciones para botones, tarjetas, modales, notificaciones toast, listas con efecto escalonado (stagger), transiciones de página en el App Router de Next.js, animaciones de entrada/salida condicionales con `AnimatePresence`, y patrones de scroll (revelación, barra de progreso, y secciones adhesivas). También incluye animaciones de layout y elementos compartidos con `layoutId`. El skill impone reglas estrictas: todo render condicional debe estar envuelto en `AnimatePresence` con una `key`, las animaciones de salida deben definirse al mismo tiempo que las de entrada, y los valores de animación deben importarse de `motion-foundations` en lugar de definirse manualmente. Está orientado a desarrolladores que trabajan con `motion/react` y `next/navigation` y requieren animaciones accesibles, seguras para SSR y de alto rendimiento. Al activarlo, el agente genera ejemplos de código completos y funcionales que siguen estos principios, evitando antipatrones comunes como la falta de `exit` o el uso excesivo de `layout` en subárboles grandes. Además, se integra con los skills complementarios `motion-foundations` (que define los tokens base) y `motion-advanced` (para interacciones más complejas como drag o gestos).

Compatible con~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/motion-patterns

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Patrones de Animación

Patrones para copiar y pegar las necesidades de animación UI más comunes. Cada patrón está construido sobre los tokens y springs de motion-foundations. No definas nuevos valores de duración o easing aquí; impórtalos.

Cuándo Activar

  • Animando un botón, tarjeta, modal o notificación toast
  • Construyendo entradas de lista con efecto escalonado (stagger)
  • Configurando transiciones de página en Next.js App Router
  • Añadiendo animaciones de entrada o salida a contenido condicional
  • Implementando revelación al hacer scroll, progreso vinculado al scroll, o secciones adhesivas
  • Construyendo tarjetas expandibles, acordeones o transiciones de elementos compartidos

Salidas

Este skill produce:

  • Animación accesible y segura para SSR para todos los componentes UI estándar
  • Renderizados condicionales envueltos en AnimatePresence con comportamiento de salida correcto
  • Componente envolvente de transición de página para Next.js App Router
  • Patrones de revelación al scroll y vinculados al scroll usando useScroll + useTransform
  • Patrones de animación de layout (layout, layoutId) para elementos que se expanden y se desvanecen

Principios

  • Cada patrón importa de motion-foundations. Sin números en crudo.
  • Cada render condicional está envuelto en AnimatePresence con una key.
  • Las animaciones de salida siempre se definen junto con las de entrada, nunca como una ocurrencia tardía.
  • layout se usa solo para desplazamientos pequeños y aislados. Los subárboles grandes usan transformaciones explícitas.

Reglas

  1. Siempre envuelve los renderizados condicionales en AnimatePresence con una key en el hijo directo. Sin una key, las animaciones de salida nunca se disparan.
  2. Siempre define exit cuando definas initial + animate. Una animación sin salida está incompleta.
  3. Usa mode="wait" en las transiciones de página. La entrada no debe comenzar hasta que la salida termine.
  4. Nunca uses layout en subárboles con más de ~5 hijos o DOM profundamente anidado. Usa transformaciones explícitas x/y.
  5. El intervalo de stagger debe mantenerse entre 0.05s y 0.10s. Por debajo se siente mecánico; por encima se siente lento.
  6. Los modales siempre deben incluir: trampa de foco, cierre con tecla Escape, bloqueo de scroll, role="dialog", aria-modal="true".
  7. Las revelaciones al scroll usan viewport={{ once: true }}. Repetir al salir del scroll distrae, no informa.
  8. Todos los valores de token se importan de motion-foundations. Sin números en línea.

Guía de Decisión

Elegir el patrón correcto

SituaciónPatrón
Elemento aparece / desapareceAnimatePresence
Lista de elementos cargando en secuenciaVariantes con stagger
Navegando entre rutasEnvolvente de transición de página
Elemento cambia de tamaño en su lugarProp layout
Mismo elemento se mueve entre contextos de páginalayoutId
Elemento entra al hacerse scroll hacia élwhileInView
Valor vinculado a la posición de scrolluseScroll + useTransform

Cuándo usar mode="wait" vs mode="sync"

ModoÚsalo cuando
waitTransiciones de página, intercambios de contenido (uno a la vez)
syncNotificaciones apiladas, elementos de lista (la superposición está bien)
popLayoutElementos eliminados de una lista con reflujo

Conceptos Clave

Contrato de AnimatePresence

Tres cosas deben ser siempre ciertas:

  1. AnimatePresence envuelve el condicional
  2. El hijo directo tiene una key
  3. El hijo tiene una prop exit

Si falta alguna de estas, la animación de salida falla silenciosamente.

layout vs layoutId

  • layout — anima el cambio de tamaño/posición del propio elemento en su lugar
  • layoutId — vincula dos elementos separados, haciendo una transición de fundido entre ellos a través de renderizados

Usa layout="position" en el texto dentro de un contenedor que se expande para evitar que el reflujo del texto se anime.

Ejemplos de Código

Retroalimentación de botón

"use client"
import { motion } from "motion/react"
import { springs, motionTokens } from "@/lib/motion-tokens"

<motion.button
  whileHover={{ scale: motionTokens.scale.pop }}
  whileTap={{ scale: motionTokens.scale.press }}
  transition={springs.snappy}
/>

Lista escalonada

"use client"
import { motion } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"

const container = {
  hidden: {},
  visible: {
    transition: {
      staggerChildren: 0.08,   // dentro de la regla 0.05–0.10
      delayChildren: 0.1,
    },
  },
}

const item = {
  hidden:  { opacity: 0, y: motionTokens.distance.md },
  visible: { opacity: 1, y: 0, transition: springs.gentle },
}

<motion.ul variants={container} initial="hidden" animate="visible">
  {items.map((i) => (
    <motion.li key={i.id} variants={item} />
  ))}
</motion.ul>

Modal

"use client"
import { motion, AnimatePresence } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"

// Envuelve en el punto de llamada:
// <AnimatePresence>{isOpen && <Modal key="modal" />}</AnimatePresence>

export function Modal({ onClose }: { onClose: () => void }) {
  return (
    <>
      {/* Superposición */}
      <motion.div
        className="fixed inset-0 bg-black/50"
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
        onClick={onClose}
      />

      {/* Panel — requisitos de accesibilidad: trampa de foco, cierre con Escape,
          bloqueo de scroll, role="dialog", aria-modal="true" */}
      <motion.div
        role="dialog"
        aria-modal="true"
        className="fixed inset-x-4 top-1/2 -translate-y-1/2 rounded-xl bg-white p-6"
        initial={{
          opacity: 0,
          scale: motionTokens.scale.press,
          y: motionTokens.distance.sm,
        }}
        animate={{ opacity: 1, scale: 1, y: 0 }}
        exit={{
          opacity: 0,
          scale: motionTokens.scale.press,
          y: motionTokens.distance.sm,
        }}
        transition={springs.gentle}
      />
    </>
  )
}

Pila de notificaciones toast

"use client"
import { motion, AnimatePresence } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"

<AnimatePresence mode="sync">
  {toasts.map((t) => (
    <motion.div
      key={t.id}
      layout
      initial={{
        opacity: 0,
        x: motionTokens.distance.xl,
        scale: motionTokens.scale.subtle,
      }}
      animate={{ opacity: 1, x: 0, scale: 1 }}
      exit={{
        opacity: 0,
        x: motionTokens.distance.xl,
        scale: motionTokens.scale.subtle,
      }}
      transition={springs.snappy}
    />
  ))}
</AnimatePresence>

Transición de página (Next.js App Router)

// components/page-transition.tsx
"use client"
import { motion, AnimatePresence } from "motion/react"
import { usePathname } from "next/navigation"
import { motionTokens } from "@/lib/motion-tokens"

const variants = {
  initial: { opacity: 0, y: motionTokens.distance.sm },
  enter:   { opacity: 1, y: 0 },
  exit:    { opacity: 0, y: -motionTokens.distance.sm },
}

export function PageTransition({ children }: { children: React.ReactNode }) {
  const pathname = usePathname()
  return (
    <AnimatePresence mode="wait">
      <motion.div
        key={pathname}
        variants={variants}
        initial="initial"
        animate="enter"
        exit="exit"
        transition={{
          duration: motionTokens.duration.normal,
          ease: motionTokens.easing.smooth,
        }}
      >
        {children}
      </motion.div>
    </AnimatePresence>
  )
}

Revelación al scroll

"use client"
import { motion } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"

<motion.div
  initial={{ opacity: 0, y: motionTokens.distance.lg }}
  whileInView={{ opacity: 1, y: 0 }}
  viewport={{ once: true, margin: "-80px" }}   // once: true — regla 7
  transition={{ duration: motionTokens.duration.slow, ease: motionTokens.easing.smooth }}
/>

Barra de progreso de scroll

"use client"
import { motion, useScroll } from "motion/react"

export function ScrollProgress() {
  const { scrollYProgress } = useScroll()
  return (
    <motion.div
      className="fixed top-0 left-0 h-1 bg-indigo-500 origin-left w-full"
      style={{ scaleX: scrollYProgress }}
    />
  )
}

Tarjeta expandible

"use client"
import { useState } from "react"
import { motion, AnimatePresence } from "motion/react"
import { springs, motionTokens } from "@/lib/motion-tokens"

export function ExpandingCard({ title, body }: { title: string; body: string }) {
  const [expanded, setExpanded] = useState(false)

  return (
    <motion.div layout onClick={() => setExpanded(!expanded)} className="cursor-pointer">
      {/* layout="position" evita que el reflujo del texto se anime */}
      <motion.h2 layout="position" className="font-semibold">
        {title}
      </motion.h2>

      <AnimatePresence>
        {expanded && (
          <motion.p
            key="body"
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            transition={{ duration: motionTokens.duration.fast }}
          >
            {body}
          </motion.p>
        )}
      </AnimatePresence>
    </motion.div>
  )
}

Fundido de elemento compartido

// Contexto de origen
<motion.img layoutId="hero-image" src={src} className="w-16 h-16 rounded" />

// Contexto de destino (mismo layoutId — motion maneja la transición)
<motion.img layoutId="hero-image" src={src} className="w-full rounded-xl" />

Acordeón

<motion.div
  initial={false}
  animate={{ opacity: open ? 1 : 0, scaleY: open ? 1 : 0 }}
  style={{ transformOrigin: "top", overflow: "hidden" }}
  transition={{
    duration: motionTokens.duration.normal,
    ease: motionTokens.easing.smooth,
  }}
> {children}
</motion.div>

Ejemplo Completo

Una lista escalonada que entra al montarse, maneja presencia condicional y respeta el movimiento reducido — combinando tokens, springs, AnimatePresence y el hook de accesibilidad de motion-foundations:

"use client"
import { useState } from "react"
import { motion, AnimatePresence } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"
import { useSafeMotion } from "@/hooks/use-reduced-motion"

const containerVariants = {
  hidden: {},
  visible: {
    transition: { staggerChildren: 0.08, delayChildren: 0.1 },
  },
}

function ListItem({ label, onRemove }: { label: string; onRemove: () => void }) {
  const safe = useSafeMotion(motionTokens.distance.sm)
  return (
    <motion.li
      variants={{
        hidden:  safe.initial,
        visible: safe.animate,
      }}
      exit={safe.exit}
      transition={springs.gentle}
      className="flex items-center justify-between p-3 rounded-lg bg-white shadow-sm"
    >
      <span>{label}</span>
      <button onClick={onRemove}>Eliminar</button>
    </motion.li>
  )
}

export function AnimatedList({ items, onRemove }: {
  items: { id: string; label: string }[]
  onRemove: (id: string) => void
}) {
  return (
    <motion.ul
      variants={containerVariants}
      initial="hidden"
      animate="visible"
      className="space-y-2"
    >
      <AnimatePresence mode="popLayout">
        {items.map((item) => (
          <ListItem
            key={item.id}
            label={item.label}
            onRemove={() => onRemove(item.id)}
          />
        ))}
      </AnimatePresence>
    </motion.ul>
  )
}

Restricciones / No Objetivos

Este skill no cubre:

  • Definiciones de tokens y springs → consulta motion-foundations
  • Interacciones de arrastre, gestos de deslizamiento, listas reordenables → consulta motion-advanced
  • Animaciones de texto (revelación de palabras/caracteres, contadores) → consulta motion-advanced
  • Dibujo de trazados SVG o morphing → consulta motion-advanced
  • Hooks de animación personalizados → consulta motion-advanced
  • Transiciones solo CSS que no usan motion/react

Antipatrones

AntipatrónRegla violadaSolución
Hijo de AnimatePresence sin keyRegla 1Añade una key estable al hijo directo
initial + animate sin exitRegla 2Siempre define los tres juntos
Transición de página sin mode="wait"Regla 3Añade mode="wait" a AnimatePresence
layout en una lista de 50 elementosRegla 4Usa mode="popLayout" o transformaciones explícitas
staggerChildren: 0.2 en una lista de 10 elementosRegla 5Limítalo a 0.08–0.10
Modal sin trampa de focoRegla 6Añade focus-trap-react o Radix Dialog
whileInView sin viewport={{ once: true }}Regla 7Las entradas repetidas distraen, no informan
transition={{ duration: 0.3 }} en líneaRegla 8Usa motionTokens.duration.normal

Skills Relacionados

  • motion-foundations — define todos los tokens, springs, el hook useSafeMotion y las protecciones SSR que cada patrón aquí importa. Debe configurarse primero.
  • motion-advanced — extiende estos patrones con arrastre, gestos, SVG, texto, hooks personalizados y secuenciación imperativa. No redefine ningún patrón de este skill.

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

Skills relacionados