Communitygithub.com

patrones-de-movimiento

Patrones de animación listos para producción en React / Next.js: botones, modales, notificaciones, listas escalonadas, transiciones de página, animaciones de salida, scroll y layout — construidos sobre tokens y springs de motion-foundations.

¿Qué es patrones-de-movimiento?

Este skill proporciona una colección completa de patrones de animación listos para copiar y pegar, diseñados para los componentes de UI más comunes en aplicaciones React y Next.js. Cada patrón está construido sobre los tokens y springs de `motion-foundations`, lo que garantiza consistencia visual, rendimiento y accesibilidad. Incluye animaciones para botones, modales, notificaciones en pila, listas con efecto escalonado, transiciones de página en el App Router de Next.js, revelaciones al hacer scroll, barras de progreso vinculadas al scroll, tarjetas expandibles y transiciones de elementos compartidos. El skill enfatiza prácticas sólidas de animación: uso obligatorio de `AnimatePresence` con `key` en renders condicionales, definición simultánea de animaciones de entrada y salida, importación de valores desde tokens en lugar de números fijos, y cumplimiento de requisitos de accesibilidad en modales. Al activarlo, el agente genera código SSR-seguro, accesible y eficiente, siguiendo reglas estrictas sobre intervalos de escalonamiento, uso del modo `wait` en transiciones de página y manejo adecuado del layout. Está diseñado para funcionar en conjunto con `motion-foundations` (tokens y springs) y para complementar `motion-advanced` (gestos, arrastre, SVG y texto).

Compatible con~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/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 Movimiento

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

Cuándo activarlo

  • Al animar un botón, tarjeta, modal o notificación toast
  • Al construir entradas de lista con efecto escalonado
  • Al configurar transiciones de página en el App Router de Next.js
  • Al añadir animaciones de entrada o salida a contenido condicional
  • Al implementar revelaciones al hacer scroll, progreso vinculado al scroll o secciones adhesivas
  • Al construir tarjetas expandibles, acordeones o transiciones de elementos compartidos

Resultados

Este skill produce:

  • Animación accesible y segura para SSR para todos los componentes de UI estándar
  • Renderizado condicional envuelto en AnimatePresence con comportamiento de salida correcto
  • Componente envoltorio de transición de página para el App Router de Next.js
  • 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 desde motion-foundations. Sin números fijos.
  • 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 idea posterior.
  • layout se usa solo para cambios pequeños y aislados. Los subárboles grandes reciben transformaciones explícitas.

Reglas

  1. Siempre envuelve los renders 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 se complete.
  4. Nunca uses layout en subárboles con más de ~5 hijos o DOM profundamente anidado. Usa transformaciones explícitas con x/y en su lugar.
  5. El intervalo de escalonamiento debe mantenerse entre 0.05s y 0.10s. Por debajo se siente mecánico; por encima se siente lento.
  6. Los modales deben incluir siempre: trampa de enfoque, 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 viewport distrae, no informa.
  8. Todos los valores de token se importan desde motion-foundations. Sin números inline.

Guía de decisión

Elegir el patrón correcto

SituaciónPatrón
Un elemento aparece / desapareceAnimatePresence
Lista de elementos cargando en secuenciaVariantes escalonadas
Navegando entre rutasEnvoltorio de transición de página
Un elemento cambia de tamaño en su lugarProp layout
Mismo elemento se mueve entre contextos de páginalayoutId
Elemento entra cuando se desplaza a la vistawhileInView
Valor vinculado a la posición de scrolluseScroll + useTransform

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

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

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 renders

Usa layout="position" en el texto dentro de un contenedor expandible 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"

// Envolver en el lugar de uso:
// <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 enfoque, 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

"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 (App Router de Next.js)

// 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 de 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 aparece al montar, 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 cubre

Este skill no cubre:

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

Antipatrones

AntipatrónRegla violadaSolución
Hijo de AnimatePresence sin keyRegla 1Añadir key estable al hijo directo
initial + animate sin exitRegla 2Definir siempre los tres juntos
Transición de página sin mode="wait"Regla 3Añadir mode="wait" a AnimatePresence
layout en una lista de 50 elementosRegla 4Usar mode="popLayout" o transformaciones explícitas
staggerChildren: 0.2 en una lista de 10 elementosRegla 5Limitar a 0.08–0.10
Modal sin trampa de enfoqueRegla 6Añadir focus-trap-react o Radix Dialog
whileInView sin viewport={{ once: true }}Regla 7Las entradas repetitivas distraen, no informan
transition={{ duration: 0.3 }} inlineRegla 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/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

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.

Skills relacionados