- Grupo: Animación
- Cuándo se activa: Animación declarativa en React/Next con Motion (variants, gestos, layout animations)
- Se instala: Opcional: llega con un paquete de skills.
Skill upstream (inglés): SKILL.upstream.md (conceptos, patrones, hooks, integración GSAP/R3F, rendimiento, pitfalls),
references/api_reference.md (API completa, 1077 líneas), assets/starter_motion/, assets/examples/,
scripts/animation_generator.py, scripts/variant_builder.py. Capa Senzu (español): references/es/next.md.
Motion es la herramienta de animación de estado en React: entradas/salidas de componentes, gestos, listas que se
reordenan, modales, tabs con indicador compartido. Duraciones, easing y “qué se anima” salen del
senzu/design-system/*/MASTER.md (skill ui-ux-pro-max); sin él, 150/250/400 ms y ease: [0.2, 0.8, 0.2, 1].
| Situación |
Skill |
Componentes React/Next: estados, gestos, AnimatePresence, layout, layoutId, stagger de listas |
motion-framer |
| Scroll narrativo, pin, scrub, parallax de varias capas, SplitText, timelines largas |
gsap-scrolltrigger |
| Vue 3 (SPA o Laravel + Inertia), Astro sin islands React |
gsap-scrolltrigger (o motion vanilla: import { animate } from "motion") |
| 3D: mover cámara/mesh |
react-three-fiber (Motion solo aporta useMotionValue como puente, ver upstream 656-686) |
| Hover/focus trivial de un botón |
CSS transition (no cargues Motion por esto) |
| Tarea |
Líneas |
Primer componente: motion.*, animate, initial, transition |
31-107 |
| Variants y propagación a hijos (stagger) |
108-150, 860-879 |
| Hover / tap / drag |
152-281, 506-542 |
| Modales, toasts, listas con salida (AnimatePresence) |
283-357, 790-824 |
Layout animations y layoutId (tabs, expandir card) |
359-424, 767-786, 840-858 |
Aparecer al hacer scroll (whileInView, useInView) |
426-465, 606-623 |
| Springs y presets |
467-504 |
Control imperativo (useAnimate, stagger) |
544-604 |
| Rendimiento y reduced motion |
722-786 |
| Errores frecuentes (6 casos) |
788-902 |
API detallada: useScroll/useTransform/useSpring |
references/api_reference.md 619-718 |
AnimatePresence modos (wait, popLayout) y usePresence |
references/api_reference.md 779-886 |
| Perfil |
Cómo |
Puntos críticos |
| Next.js App Router + React |
npm i motion → import { motion, AnimatePresence } from "motion/react" (upstream aún importa framer-motion; equivale) |
Todo componente con Motion lleva "use client"; el Server Component padre pasa datos como props. useReducedMotion() en cada patrón; LazyMotion + m + domAnimation para reducir bundle en páginas públicas. Detalle: references/es/next.md. |
| Vue 3 / Laravel + Inertia |
No uses Motion for React |
gsap-scrolltrigger (gsap-scrolltrigger/references/es/vue.md) o motion vanilla (animate(), inView(), scroll()) para casos puntuales. |
| Astro |
Solo dentro de islands React (client:visible) |
Si la página no tiene React, gsap-scrolltrigger/references/es/astro.md. |
| shadcn/ui |
Envolver: <motion.div> alrededor del componente o asChild + motion.button |
No reescribas los componentes de components/ui/*; anima contenedores. |
useReducedMotion() en todo patrón con desplazamiento, escala o stagger: con reduce, solo opacidad o duration: 0;
el estado final siempre visible (nunca contenido oculto por initial en SSR/sin JS: usa initial={false} o
whileInView con viewport={{ once: true }}).
- Animar solo
x/y/scale/rotate/opacity/clipPath; nada de width/height/top/left/boxShadow animados (upstream 724-746).
layout / layoutId solo donde el cambio de layout sea visible y en pocos elementos (< 20); en listas largas,
opacity + exit sin layout. layout="position" cuando el tamaño no cambia.
- Toda salida requiere
AnimatePresence con key estable (id, no índice) y el motion.* como hijo directo.
- Transiciones de gesto van dentro del propio
whileHover/whileTap (upstream 881-902).
- Limpieza:
useAnimate/animate() se cancelan en el cleanup del efecto; useScroll con target referenciado
a un elemento montado; sin listeners de scroll manuales.
- Duraciones: micro ≤ 200 ms, entradas ≤ 500 ms, page transitions ≤ 400 ms; sin intros bloqueantes.
- Un solo sistema por interacción: no mezclar GSAP y Motion sobre el mismo nodo/propiedad.
| Necesidad |
Patrón |
| Lista que aparece escalonada |
variants contenedor staggerChildren: 0.06 + hijos hidden/visible (upstream 127-150) |
| Modal / drawer con salida |
AnimatePresence + motion.div overlay y panel, key fijo; foco gestionado por Radix/shadcn |
| Tabs con subrayado que se desliza |
motion.span layoutId="tab-indicator" dentro del tab activo (upstream 395-408) |
| Card que se expande a detalle |
layoutId compartido + AnimatePresence; en Next, misma ruta (no entre páginas) |
| Botón con feedback |
whileHover={{ scale: 1.03 }} whileTap={{ scale: 0.97 }}, transición 150 ms |
| Sección que aparece al scroll |
whileInView + viewport={{ once: true, amount: 0.3 }} |
| Progreso de lectura / parallax suave |
useScroll + useTransform + useSpring (references/es/next.md §5) |
| Transición entre rutas Next |
template.tsx con motion.div de entrada; salida solo si compensa (references/es/next.md §2) |
Antes del código: patrón elegido, dónde va "use client", comportamiento con reduced-motion, qué se limpia.
Después: código + verificación en navegador (375 y 1440, consola limpia, sin parpadeo de hidratación).