/motion-ui
Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.
$ npx -y skills add affaan-m/ECC --skill motion-ui --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/motion-ui
Context preview
The summary Claude sees to decide when to auto-load this skill.
Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.
SKILL.md
motion-ui.SKILL.mdname: motion-ui
description: "Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns."
metadata:
origin: ECC
Motion System v4.2
Production-ready UI motion system for React / Next.js.
Focused on **performance, accessibility, and usability** — not decoration.
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)
Appropriate Scenarios
- Interactive components (buttons, modals, menus)
- State transitions (loading → loaded, open → closed)
- Navigation and layout continuity (shared elements, crossfade)
Considerations
- **Accessibility**: Always support reduced motion
- **Device adaptation**: Adjust for low-end devices
- **Performance trade-offs**: Prefer responsiveness over visual smoothness
Avoid Using Motion When
- It is purely decorative
- It reduces usability or clarity
- It impacts performance negatively
---
How It Works
Core Principle
Motion must:
- Guide attention
- Communicate state
- Preserve spatial continuity
If it does none → remove it.
---
Installation
npm install motion
---
Version
- `motion/react` - default for current Motion for React projects (package: `motion`)
- `framer-motion` - legacy import path for projects that still depend on Framer Motion
**Do not mix.** Mixing causes conflicting internal schedulers and broken `AnimatePresence` contexts — components from one package will not coordinate exit animations with components from the other.
To check which version your project uses:
cat package.json | grep -E '"motion"|"framer-motion"'
Always import from one source consistently:
// Correct (modern)
import { motion, AnimatePresence } from "motion/react"
// Correct (legacy)
import { motion, AnimatePresence } from "framer-motion"
// Never mix both in the same project---
Motion Tokens
// motionTokens.ts
export const motionTokens = {
duration: {
fast: 0.18,
normal: 0.35,
slow: 0.6
},
// Use these as the `ease` value inside a `transition` object:
// transition={{ duration: motionTokens.duration.normal, ease: motionTokens.easing.smooth }}
easing: {
smooth: [0.22, 1, 0.36, 1] as [number, number, number, number],
sharp: [0.4, 0, 0.2, 1] as [number, number, number, number]
},
distance: {
sm: 8,
md: 16,
lg: 24
}
}Usage example:
import { motionTokens } from "@/lib/motionTokens"
<motion.div
initial={{ opacity: 0, y: motionTokens.distance.md }}
animate={{ opacity: 1, y: 0 }}
transition={{
duration: motionTokens.duration.normal,
ease: motionTokens.easing.smooth
}}
/>---
Performance Rules
**Safe**
- transform
- opacity
**Avoid**
- width / height
- top / left
Rule: responsiveness > smoothness
---
Device Adaptation
The heuristic combines CPU core count **and** available memory for a more reliable signal. `deviceMemory` is available on Chrome/Android; the fallback covers Safari and Firefox.
const isLowEnd =
typeof navigator !== "undefined" && (
// Low memory (Chrome/Android only; undefined elsewhere → treat as capable)
(navigator.deviceMemory !== undefined && navigator.deviceMemory <= 2) ||
// Few cores AND no memory API (covers Safari/Firefox on weak hardware)
(navigator.deviceMemory === undefined && navigator.hardwareConcurrency <= 4)
)
const duration = isLowEnd ? 0.2 : 0.4---
Accessibility
JS (useReducedMotion)
import { motion, useReducedMotion } from "motion/react"
export function FadeIn() {
const reduce = useReducedMotion()
return (
<motion.div
initial={{ opacity: 0, y: reduce ? 0 : 24 }}
animate={{ opacity: 1, y: 0 }}
/>
)
}CSS
@media (prefers-reduced-motion: reduce) {
.motion-safe-transition {
transition: opacity 0.2s;
}
.motion-reduce-transform {
transform: none !important;
}
}Tailwind
<div class="motion-safe:animate-fade motion-reduce:opacity-100"></div>
---
Architecture & Patterns
Core Patterns
| Scenario | Pattern | |---|---| | Hover feedback | `whileHover` | | Tap / press feedback | `whileTap` | | Reveal on scroll | `whileInView` | | Scroll-linked value | `useScroll` + `useTransform` | | Conditional mount/unmount | `AnimatePresence` | | Small layout shifts (single element, < ~300px change) | `layout` prop | | Large layout shifts or full-page reflows | Avoid `layout`; use CSS transitions or page-level routing instead | | Complex, imperative sequences | `useAnimate` |
> **Why avoid `layout` on large containers?** Framer's layout animation uses `transform` to reconcile positions, but on elements that span the full viewport or trigger deep reflow, the measurement cost causes visible jank and CLS. Prefer CSS Grid/Flexbox transitions or coordinate with `layoutId` on specific child elements only.
Layout & Transitions
- Shared element transitions → `layoutId` (must be unique per mounted instance)
- Enter / exit transitions → `AnimatePresence` (see `mode` guidance below)
AnimatePresence `mode`
Always specify `mode` explicitly — the default (`"sync"`) runs enter and exit simultaneously, which causes visual overlap in most UI patterns.
| `mode` | When to use | |---|---| | `"wait"` | Exit completes before enter starts. Use for **modals, toasts, page transitions**. | | `"sync"` (default) | Enter and exit overlap. Use only when overlap is intentional (e.g., crossfade carousels). | | `"popLayout"` | Exiting element is popped out of flow immediately; remaining items animate to fill. Use for **lists, tabs, dismissible cards**. |
// Modal — always use "wait"
<AnimatePresence mode="wait">
{open && <Modal key="modal" />}
</AnimatePresence>
// Dismissible list item — use "popLayoutRead more
name: motion-ui description: "Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns." metadata: origin: ECC
Motion System v4.2
Production-ready UI motion system for React / Next.js.
Focused on **performance, accessibility, and usability** — not decoration.
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)
Appropriate Scenarios
- Interactive components (buttons, modals, menus)
- State transitions (loading → loaded, open → closed)
- Navigation and layout continuity (shared elements, crossfade)
Considerations
- **Accessibility**: Always support reduced motion
- **Device adaptation**: Adjust for low-end devices
- **Performance trade-offs**: Prefer responsiveness over visual smoothness
Avoid Using Motion When
- It is purely decorative
- It reduces usability or clarity
- It impacts performance negatively
---
How It Works
Core Principle
Motion must:
- Guide attention
- Communicate state
- Preserve spatial continuity
If it does none → remove it.
---
Installation
npm install motion
---
Version
- `motion/react` - default for current Motion for React projects (package: `motion`)
- `framer-motion` - legacy import path for projects that still depend on Framer Motion
**Do not mix.** Mixing causes conflicting internal schedulers and broken `AnimatePresence` contexts — components from one package will not coordinate exit animations with components from the other.
To check which version your project uses:
cat package.json | grep -E '"motion"|"framer-motion"'
Always import from one source consistently:
// Correct (modern)
import { motion, AnimatePresence } from "motion/react"
// Correct (legacy)
import { motion, AnimatePresence } from "framer-motion"
// Never mix both in the same project---
Motion Tokens
// motionTokens.ts
export const motionTokens = {
duration: {
fast: 0.18,
normal: 0.35,
slow: 0.6
},
// Use these as the `ease` value inside a `transition` object:
// transition={{ duration: motionTokens.duration.normal, ease: motionTokens.easing.smooth }}
easing: {
smooth: [0.22, 1, 0.36, 1] as [number, number, number, number],
sharp: [0.4, 0, 0.2, 1] as [number, number, number, number]
},
distance: {
sm: 8,
md: 16,
lg: 24
}
}Usage example:
import { motionTokens } from "@/lib/motionTokens"
<motion.div
initial={{ opacity: 0, y: motionTokens.distance.md }}
animate={{ opacity: 1, y: 0 }}
transition={{
duration: motionTokens.duration.normal,
ease: motionTokens.easing.smooth
}}
/>---
Performance Rules
**Safe**
- transform
- opacity
**Avoid**
- width / height
- top / left
Rule: responsiveness > smoothness
---
Device Adaptation
The heuristic combines CPU core count **and** available memory for a more reliable signal. `deviceMemory` is available on Chrome/Android; the fallback covers Safari and Firefox.
const isLowEnd =
typeof navigator !== "undefined" && (
// Low memory (Chrome/Android only; undefined elsewhere → treat as capable)
(navigator.deviceMemory !== undefined && navigator.deviceMemory <= 2) ||
// Few cores AND no memory API (covers Safari/Firefox on weak hardware)
(navigator.deviceMemory === undefined && navigator.hardwareConcurrency <= 4)
)
const duration = isLowEnd ? 0.2 : 0.4---
Accessibility
JS (useReducedMotion)
import { motion, useReducedMotion } from "motion/react"
export function FadeIn() {
const reduce = useReducedMotion()
return (
<motion.div
initial={{ opacity: 0, y: reduce ? 0 : 24 }}
animate={{ opacity: 1, y: 0 }}
/>
)
}CSS
@media (prefers-reduced-motion: reduce) {
.motion-safe-transition {
transition: opacity 0.2s;
}
.motion-reduce-transform {
transform: none !important;
}
}Tailwind
<div class="motion-safe:animate-fade motion-reduce:opacity-100"></div>
---
Architecture & Patterns
Core Patterns
| Scenario | Pattern | |---|---| | Hover feedback | `whileHover` | | Tap / press feedback | `whileTap` | | Reveal on scroll | `whileInView` | | Scroll-linked value | `useScroll` + `useTransform` | | Conditional mount/unmount | `AnimatePresence` | | Small layout shifts (single element, < ~300px change) | `layout` prop | | Large layout shifts or full-page reflows | Avoid `layout`; use CSS transitions or page-level routing instead | | Complex, imperative sequences | `useAnimate` |
> **Why avoid `layout` on large containers?** Framer's layout animation uses `transform` to reconcile positions, but on elements that span the full viewport or trigger deep reflow, the measurement cost causes visible jank and CLS. Prefer CSS Grid/Flexbox transitions or coordinate with `layoutId` on specific child elements only.
Layout & Transitions
- Shared element transitions → `layoutId` (must be unique per mounted instance)
- Enter / exit transitions → `AnimatePresence` (see `mode` guidance below)
AnimatePresence `mode`
Always specify `mode` explicitly — the default (`"sync"`) runs enter and exit simultaneously, which causes visual overlap in most UI patterns.
| `mode` | When to use | |---|---| | `"wait"` | Exit completes before enter starts. Use for **modals, toasts, page transitions**. | | `"sync"` (default) | Enter and exit overlap. Use only when overlap is intentional (e.g., crossfade carousels). | | `"popLayout"` | Exiting element is popped out of flow immediately; remaining items animate to fill. Use for **lists, tabs, dismissible cards**. |
// Modal — always use "wait"
<AnimatePresence mode="wait">
{open && <Modal key="modal" />}
</AnimatePresence>
// Dismissible list item — use "popLayoutYour agent can write code, but ECC gives it a coordinated engineering system and toolbox: it plans before it builds, verifies changes with tests, reviews its own work from a fresh context, remembers what matters, and turns repeated wins into reusable skills
Repo: affaan-m/ECC
Other skills on ecc.
- /everything-claude-code
Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.
Open skill - /accessibility
Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA
Open skill - /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
Open skill - /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
Open skill - /agent-harness-construction
Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates.
Open skill - /agent-introspection-debugging
Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports.
Open skill

