From 957d5e72f47c5b530aa26210070b1c92d0b79444 Mon Sep 17 00:00:00 2001 From: Jeff Date: Sat, 9 May 2026 22:35:42 +0530 Subject: [PATCH] Revise motion UI documentation structure and content Refactor motion UI documentation for clarity and consistency. Update sections on usage, scenarios, and examples. --- skills/motion_ui/skill.md | 385 +++++++++++++++----------------------- 1 file changed, 155 insertions(+), 230 deletions(-) diff --git a/skills/motion_ui/skill.md b/skills/motion_ui/skill.md index 7fdf9e41..e7fe30b6 100644 --- a/skills/motion_ui/skill.md +++ b/skills/motion_ui/skill.md @@ -1,3 +1,4 @@ +````md --- name: motion-ui description: "Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns." @@ -11,66 +12,64 @@ Focused on **performance, accessibility, and usability** — not decoration. **by Jatan** +--- + ## When to Use Use this motion system when motion: -* Guides attention (e.g., onboarding, key actions) -* Communicates state (loading, success, error, transitions) -* Preserves spatial continuity (layout changes, navigation) +- Guides attention (onboarding, primary actions) +- Communicates state (loading, success, error, transitions) +- Preserves spatial continuity (navigation, layout changes) ### Appropriate Scenarios -* Interactive components (buttons, modals, menus) -* State transitions (loading → loaded, open → closed) -* Navigation and layout continuity (shared elements, crossfade) +- Interactive UI (buttons, modals, menus) +- State transitions (open/close, loading states) +- Navigation transitions and shared elements ### Considerations -* **Accessibility**: Always support reduced motion -* **Device adaptation**: Adjust for low-end devices -* **Performance trade-offs**: Prefer responsiveness over visual smoothness +- Accessibility must be preserved (reduced motion support) +- Low-end device performance must be respected +- Prefer responsiveness over visual smoothness -### Avoid Using Motion When +### Avoid Motion When -* It is purely decorative -* It reduces usability or clarity -* It impacts performance negatively +- It is purely decorative +- It reduces clarity or usability +- It impacts performance --- -## How It Works - -### Core Principle +## Core Principle Motion must: -* Guide attention -* Communicate state -* Preserve spatial continuity +- Guide attention +- Communicate state +- Preserve spatial continuity If it does none → remove it. --- -### Installation +## Installation ```bash npm install motion -``` +```` --- -### Version +## Versions * `motion/react` → default -* `framer-motion` → legacy - -Do not mix. +* `framer-motion` → legacy (do not mix) --- -### Motion Tokens +## Motion Tokens ```ts export const motionTokens = { @@ -93,23 +92,25 @@ export const motionTokens = { --- -### Performance Rules +## Performance Rules -**Safe** +### Safe Properties * transform * opacity -**Avoid** +### Avoid -* width / height -* top / left +* width +* height +* top +* left Rule: responsiveness > smoothness --- -### Device Adaptation +## Device Adaptation ```ts const isLowEnd = @@ -121,22 +122,26 @@ const duration = isLowEnd ? 0.2 : 0.4 --- -### Accessibility +## Accessibility -#### JS (useReducedMotion) +### Reduced Motion (React) ```tsx import { motion, useReducedMotion } from "motion/react" const reduce = useReducedMotion() - +export function Example() { + return ( + + ) +} ``` -#### CSS +### CSS ```css @media (prefers-reduced-motion: reduce) { @@ -150,7 +155,7 @@ const reduce = useReducedMotion() } ``` -#### Tailwind +### Tailwind ```html
@@ -158,74 +163,79 @@ const reduce = useReducedMotion() --- -### Architecture & Patterns +## Core Patterns -#### Core Patterns - -* Hover → `whileHover` -* Tap → `whileTap` -* In view → `whileInView` -* Scroll linked → `useScroll` -* Conditional → `AnimatePresence` -* Layout small → `layout` -* Layout large → avoid -* Complex → `useAnimate` - -#### Layout & Transitions - -* Shared transitions → `layoutId` -* Presence transitions → `AnimatePresence` +* hover → whileHover +* tap → whileTap +* in-view → whileInView +* scroll → useScroll +* conditional → AnimatePresence +* small layout → layout +* large layout → avoid +* complex → useAnimate --- -### Advanced Patterns (Concepts) +## Layout System -* Parallax (scroll-linked transforms) -* Scroll storytelling (sticky sections) -* 3D tilt (pointer-based transforms) -* Crossfade (shared layoutId) -* Progressive reveal (clip-path) -* Skeleton loading (looped opacity) -* Micro-interactions (hover/tap feedback) -* Spring system (physics-based motion) +* layoutId → shared transitions +* AnimatePresence → mount/unmount transitions --- -### Modal Essentials +## Advanced Patterns -* Focus trap -* Escape close -* Scroll lock -* ARIA roles +* Parallax scrolling +* Scroll storytelling sections +* 3D pointer tilt +* Crossfade transitions +* Clip-path reveals +* Skeleton loading loops +* Micro-interactions +* Spring physics motion -#### Minimal Example +--- + +## Modal System (Production Safe) ```tsx -import { useEffect, useRef } from "react" +import { useEffect, useRef, useState } from "react" import { motion, AnimatePresence } from "motion/react" -// Placeholder hooks (implement or replace with libraries) +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] - function handleKey(e: KeyboardEvent) { + 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() + e.preventDefault() + last.focus() } else if (!e.shiftKey && document.activeElement === last) { - e.preventDefault(); first?.focus() + e.preventDefault() + first.focus() } } el.addEventListener("keydown", handleKey) - first?.focus() return () => el.removeEventListener("keydown", handleKey) }, [active, ref]) } @@ -233,25 +243,31 @@ function useFocusTrap(ref: React.RefObject, active: boolean) { 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 } + + return () => { + document.body.style.overflow = prev + } }, [active]) } -export function Modal({ open, closeModal }: { open: boolean; closeModal: () => void }) { +export function Modal({ open, onClose }: ModalProps) { const ref = useRef(null) useFocusTrap(ref, open) useScrollLock(open) useEffect(() => { - function onKey(e: KeyboardEvent) { - if (e.key === "Escape") closeModal() + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") onClose() } - if (open) window.addEventListener("keydown", onKey) - return () => window.removeEventListener("keydown", onKey) - }, [open, closeModal]) + + if (open) window.addEventListener("keydown", onKeyDown) + + return () => window.removeEventListener("keydown", onKeyDown) + }, [open, onClose]) return ( @@ -259,6 +275,7 @@ export function Modal({ open, closeModal }: { open: boolean; closeModal: () => v v exit={{ scale: 0.95, opacity: 0 }} className="bg-white p-6 rounded" > - +
)} ) } +``` -// Usage -export function Example() { - const [open, setOpen] = React.useState(false) - const openModal = () => setOpen(true) - const closeModal = () => setOpen(false) +--- - return ( - <> - - - - ) +## 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 } ``` --- -### SSR Safety - -* Match initial states -* Avoid implicit animation origins - ---- - -### Debugging - -Check: - -* Wrong import -* Missing `"use client"` -* Missing `key` -* Hydration mismatch -* Layout misuse -* State-driven animation - ---- - -### QA - -* No CLS -* Keyboard works -* Focus trapped -* ARIA correct -* Reduced motion works -* No hydration warnings -* Animations stop on unmount - ---- - -### Anti-Patterns - -* Animating layout properties -* Infinite animations without purpose -* Over-staggering lists -* Ignoring reduced motion -* Using motion for decoration - ---- - -### Philosophy - -Motion is interaction design. - ---- - -### Final Rule - -> If motion does not improve UX → remove it. - ---- - -## Examples - -### Button Interaction +## Skeleton Loading ```tsx import { motion } from "motion/react" -export function Button() { - return ( - - Click me - - ) -} -``` - ---- - -### Reduced Motion Example - -```tsx -import { motion, useReducedMotion } from "motion/react" - -export function FadeIn() { - const reduce = useReducedMotion() - +export function Skeleton() { return ( ) } @@ -390,7 +332,19 @@ export function FadeIn() { --- -### Stagger List +## Shared Layout + +```tsx +import { motion } from "motion/react" + +export function Shared() { + return +} +``` + +--- + +## Stagger List ```tsx import { motion } from "motion/react" @@ -410,8 +364,10 @@ const item = { export function List() { return ( - {[1,2,3].map(i => ( - Item {i} + {[1, 2, 3].map(i => ( + + Item {i} + ))} ) @@ -420,68 +376,37 @@ export function List() { --- -### Modal with AnimatePresence +## Debug Checklist -```tsx -import { motion, AnimatePresence } from "motion/react" - -export function Modal({ open }) { - return ( - - {open && ( - - )} - - ) -} -``` +* 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 --- -### Scroll Parallax +## Anti-Patterns -```tsx -import { useScroll, useTransform, motion } from "motion/react" - -export function Parallax() { - const { scrollYProgress } = useScroll() - const y = useTransform(scrollYProgress, [0, 1], [0, -80]) - - return -} -``` +* animating layout (width/height) +* decorative motion +* infinite motion without purpose +* ignoring reduced motion +* over-staggering lists --- -### Skeleton Loading +## Philosophy -```tsx -import { motion } from "motion/react" - -export function Skeleton() { - return ( - - ) -} -``` +Motion is interaction design. --- -### Shared Layout (Crossfade) +## Final Rule -```tsx -import { motion } from "motion/react" +> If motion does not improve UX → remove it. -export function Shared() { - return -} ``` - +```