````md --- name: motion-ui description: "Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns." --- # Motion System v4.1 Production-ready UI motion system for React / Next.js. Focused on **performance, accessibility, and usability** — not decoration. **by Jatan** --- ## When to Use Use this motion system when motion: - Guides attention (onboarding, primary actions) - Communicates state (loading, success, error, transitions) - Preserves spatial continuity (navigation, layout changes) ### Appropriate Scenarios - Interactive UI (buttons, modals, menus) - State transitions (open/close, loading states) - Navigation transitions and shared elements ### Considerations - Accessibility must be preserved (reduced motion support) - Low-end device performance must be respected - Prefer responsiveness over visual smoothness ### Avoid Motion When - It is purely decorative - It reduces clarity or usability - It impacts performance --- ## Core Principle Motion must: - Guide attention - Communicate state - Preserve spatial continuity If it does none → remove it. --- ## Installation ```bash npm install motion ```` --- ## Versions * `motion/react` → default * `framer-motion` → legacy (do not mix) --- ## Motion Tokens ```ts export const motionTokens = { duration: { fast: 0.18, normal: 0.35, slow: 0.6 }, easing: { smooth: [0.22, 1, 0.36, 1], sharp: [0.4, 0, 0.2, 1] }, distance: { sm: 8, md: 16, lg: 24 } } ``` --- ## Performance Rules ### Safe Properties * transform * opacity ### Avoid * width * height * top * left Rule: responsiveness > smoothness --- ## Device Adaptation ```ts const isLowEnd = typeof navigator !== "undefined" && navigator.hardwareConcurrency <= 4 const duration = isLowEnd ? 0.2 : 0.4 ``` --- ## Accessibility ### Reduced Motion (React) ```tsx import { motion, useReducedMotion } from "motion/react" const reduce = useReducedMotion() export function Example() { return ( ) } ``` ### CSS ```css @media (prefers-reduced-motion: reduce) { .motion-safe-transition { transition: opacity 0.2s; } .motion-reduce-transform { transform: none !important; } } ``` ### Tailwind ```html
``` --- ## Core Patterns * hover → whileHover * tap → whileTap * in-view → whileInView * scroll → useScroll * conditional → AnimatePresence * small layout → layout * large layout → avoid * complex → useAnimate --- ## Layout System * layoutId → shared transitions * AnimatePresence → mount/unmount transitions --- ## Advanced Patterns * Parallax scrolling * Scroll storytelling sections * 3D pointer tilt * Crossfade transitions * Clip-path reveals * Skeleton loading loops * Micro-interactions * Spring physics motion --- ## Modal System (Production Safe) ```tsx import { useEffect, useRef, useState } from "react" import { motion, AnimatePresence } from "motion/react" type ModalProps = { open: boolean onClose: () => void } function useFocusTrap(ref: React.RefObject, active: boolean) { useEffect(() => { if (!active || !ref.current) return const el = ref.current const focusable = el.querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ) const first = focusable[0] const last = focusable[focusable.length - 1] if (first) first.focus() const handleKey = (e: KeyboardEvent) => { if (e.key !== "Tab") return if (!first || !last) return if (e.shiftKey && document.activeElement === first) { e.preventDefault() last.focus() } else if (!e.shiftKey && document.activeElement === last) { e.preventDefault() first.focus() } } el.addEventListener("keydown", handleKey) return () => el.removeEventListener("keydown", handleKey) }, [active, ref]) } function useScrollLock(active: boolean) { useEffect(() => { if (!active) return const prev = document.body.style.overflow document.body.style.overflow = "hidden" return () => { document.body.style.overflow = prev } }, [active]) } export function Modal({ open, onClose }: ModalProps) { const ref = useRef(null) useFocusTrap(ref, open) useScrollLock(open) useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") onClose() } if (open) window.addEventListener("keydown", onKeyDown) return () => window.removeEventListener("keydown", onKeyDown) }, [open, onClose]) return ( {open && ( )} ) } ``` --- ## Scroll Parallax ```tsx import { useScroll, useTransform, motion } from "motion/react" export function Parallax() { const { scrollYProgress } = useScroll() const y = useTransform(scrollYProgress, [0, 1], [0, -80]) return } ``` --- ## Skeleton Loading ```tsx import { motion } from "motion/react" export function Skeleton() { return ( ) } ``` --- ## Shared Layout ```tsx import { motion } from "motion/react" export function Shared() { return } ``` --- ## Stagger List ```tsx import { motion } from "motion/react" const container = { hidden: {}, visible: { transition: { staggerChildren: 0.08 } } } const item = { hidden: { opacity: 0, y: 10 }, visible: { opacity: 1, y: 0 } } export function List() { return ( {[1, 2, 3].map(i => ( Item {i} ))} ) } ``` --- ## Debug Checklist * correct import (`motion/react`) * `"use client"` in Next.js * no missing keys * no layout shift (CLS) * no hydration mismatch * reduced motion works * keyboard navigation works --- ## Anti-Patterns * animating layout (width/height) * decorative motion * infinite motion without purpose * ignoring reduced motion * over-staggering lists --- ## Philosophy Motion is interaction design. --- ## Final Rule > If motion does not improve UX → remove it. ``` ```