````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.
```
```