motion-foundations
affaan-m/everything-claude-code
Foundation layer for React/Next.js animations: tokens, springs, accessibility rules, and SSR safety using motion/react.
What is motion-foundations?
Motion Foundations establishes the base system for all animation work in React/Next.js projects using motion/react. It defines reusable tokens (durations, easings, distances, scales), spring presets, accessibility enforcement (prefers-reduced-motion), device adaptation, and SSR-safe patterns. Load this skill before any animated component work.
- Provides shared motionTokens object with 5 duration levels, 4 easing curves, 5 distance values, and 3 scale presets
- Exports 5 named spring configurations (snappy, gentle, bouncy, instant, release) for consistent physics-based motion
- Implements shouldAnimate() gate that respects prefers-reduced-motion and low-end device detection
- Enforces accessibility via useReducedMotion hook that disables transforms and limits opacity transitions
- Ensures SSR hydration safety by requiring initial prop to match server-rendered output
- Provides motionConfig runtime flags for device capability and user preference detection
How to install motion-foundations
npx skills add https://github.com/affaan-m/everything-claude-code --skill motion-foundations- motion/react package installed (not framer-motion)
- React 18+ with Next.js or similar SSR-capable framework
- TypeScript recommended for token type safety
How to use motion-foundations
- 1.Create lib/motion-tokens.ts with motionTokens object and springs preset map
- 2.Create lib/motion-config.ts with motionConfig object containing isLowEnd(), prefersReduced(), shouldAnimate(), and duration() methods
- 3.Create hooks/use-reduced-motion.tsx hook that wraps useReducedMotion() and returns safe initial/animate/exit states
- 4.Add 'use client' directive to all files importing from motion/react
- 5.Use motionTokens and springs in component files instead of hardcoded values
- 6.Guard window/navigator access with typeof window !== 'undefined' checks
- 7.Ensure initial prop always matches server-rendered output to prevent hydration mismatches
Use cases
- Setting up animation tokens and spring presets at project start before building animated components
- Implementing prefers-reduced-motion support to disable transforms and use opacity-only fades for accessibility
- Debugging hydration mismatches when animation initial states don't match server output
- Adapting animations for low-end devices by reducing duration or skipping non-essential animations
- Creating fade-in, slide-in, and scale animations that respect all accessibility and performance constraints
- React/Next.js developers building animated UI components
- Teams implementing accessibility-first animation systems
- Projects requiring SSR-safe animations without hydration warnings
- Developers optimizing animation performance on low-end devices
motion-foundations FAQ
Always use motion/react only. Never import from framer-motion or mix the two in the same tree. This is a non-negotiable rule.
Disable all transforms and use opacity-only fades at ≤0.2s duration as the only permitted fallback. The useReducedMotion hook handles this automatically.
The initial prop must always match what the server renders. If the server renders opacity:1, initial must also be opacity:1. Use a mounted state or AnimatePresence to defer animations to client-side.
Disable animation when prefers-reduced-motion is true, when isLowEnd() is true for non-essential animations, when the element is off-screen, or when the animation has no UX purpose.
No. All duration and easing values must come from motionTokens, and all spring configs must come from the springs map. Hardcoded values are forbidden.
Full instructions (SKILL.md)
Source of truth, from affaan-m/everything-claude-code.
name: motion-foundations description: Motion tokens, spring presets, performance rules, device adaptation, accessibility enforcement, and SSR safety for React / Next.js using motion/react. Foundation layer — all other motion skills depend on this. version: 1.0 tags: [motion, animation, performance, accessibility] category: frontend author: jeff
Motion Foundations
The base layer of the motion system. Defines every value, constraint, and
rule that downstream skills (motion-patterns, motion-advanced) inherit.
Load this skill before any animation work begins.
When to Activate
- Starting any animated component from scratch
- Setting up tokens, spring presets, or easing values
- Implementing
prefers-reduced-motionsupport - Debugging hydration mismatches from animation initial states
- Evaluating whether an animation should exist at all
Outputs
This skill produces:
- A shared
motionTokensobject (duration, easing, distance, scale) - A shared
springspreset map (5 named configs) - A
shouldAnimate()gate used by all components - Accessibility-compliant animation defaults via
useReducedMotion - SSR-safe initial states with zero hydration warnings
Principles
Motion must do at least one of the following or it must be removed:
- Guide attention
- Communicate state
- Preserve spatial continuity
Responsiveness always outranks smoothness. A 60 fps animation that causes input delay is worse than no animation.
Rules
These are non-negotiable. They apply to every component in the system.
- Use
motion/reactonly. Never import fromframer-motion. Never mix the two in the same tree. initialmust match server output. If the server rendersopacity: 1, theinitialprop must also beopacity: 1. No exceptions.- Reduced motion overrides everything. When
useReducedMotion()returnstrueorprefersReducedistrue, all transforms are disabled. Opacity-only fades at ≤ 0.2s are the only permitted fallback. - Never animate layout properties.
width,height,top,left,margin,paddingare banned fromanimate. Usetransformandopacityonly. - All token values come from
motionTokens. Hardcoded durations and easings in component files are forbidden. - All spring configs come from the
springsmap. Inlinestiffness/dampingvalues are forbidden. "use client"is required on every file that imports frommotion/react.- Never read
windowornavigatorat module level. Always guard withtypeof window !== "undefined".
Decision Guidance
Choosing a duration
| Token | Use when |
|---|---|
instant | Tooltip show/hide, focus ring, badge update |
fast | Button feedback, icon swap, chip toggle |
normal | Modal open, card expand, page element enter |
slow | Hero entrance, full-page transition |
crawl | Deliberate storytelling; use sparingly |
Choosing a spring
| Preset | Use when |
|---|---|
snappy | Default UI — buttons, chips, nav items |
gentle | Cards, modals, panels landing softly |
bouncy | Playful moments — empty states, onboarding |
instant | Tooltips, popovers, dropdowns |
release | Drag release — natural physics feel |
When to disable animation entirely
Disable (make shouldAnimate() return false) when:
prefersReducedistrueisLowEndistrueand the animation is non-essential- The element is off-screen and will never enter the viewport
- The animation is purely decorative with no UX purpose
Core Concepts
Token system
// lib/motion-tokens.ts
export const motionTokens = {
duration: {
instant: 0.08,
fast: 0.18,
normal: 0.35,
slow: 0.6,
crawl: 1.0,
},
easing: {
smooth: [0.22, 1, 0.36, 1],
sharp: [0.4, 0, 0.2, 1],
bounce: [0.34, 1.56, 0.64, 1],
linear: [0, 0, 1, 1],
},
distance: {
xs: 4,
sm: 8,
md: 16,
lg: 24,
xl: 48,
},
scale: {
subtle: 0.98,
press: 0.95,
pop: 1.04,
},
}
export const springs = {
snappy: { type: "spring", stiffness: 300, damping: 30 },
gentle: { type: "spring", stiffness: 120, damping: 14 },
bouncy: { type: "spring", stiffness: 400, damping: 10 },
instant: { type: "spring", stiffness: 600, damping: 35 },
release: { type: "spring", stiffness: 200, damping: 20, restDelta: 0.001 },
}
Runtime flags
// lib/motion-config.ts
export const motionConfig = {
isLowEnd() {
return (
typeof navigator !== "undefined" &&
navigator.hardwareConcurrency <= 4
)
},
prefersReduced() {
return (
typeof window !== "undefined" &&
window.matchMedia("(prefers-reduced-motion: reduce)").matches
)
},
shouldAnimate({ essential = false } = {}) {
if (this.prefersReduced()) return false
if (!essential && this.isLowEnd()) return false
return true
},
duration() {
return this.isLowEnd() || this.prefersReduced()
? motionTokens.duration.instant
: motionTokens.duration.normal
},
}
Accessibility
Priority order (highest to lowest):
prefers-reduced-motion: reduce— disables all transforms, limits opacity transitions to ≤ 0.2s- Low-end device detection — reduces duration, removes non-essential animations
- Design preference — everything else
Motion must degrade gracefully. It must never disappear abruptly in a way that causes layout shift or confuses orientation.
// hooks/use-reduced-motion.tsx
"use client"
import { useReducedMotion } from "motion/react"
export function useSafeMotion(fullY: number = 16) {
const reduce = useReducedMotion()
return {
initial: { opacity: 0, y: reduce ? 0 : fullY },
animate: { opacity: 1, y: 0 },
exit: { opacity: 0, y: reduce ? 0 : -fullY },
}
}
/* globals.css */
@media (prefers-reduced-motion: reduce) {
.motion-safe-transition { transition: opacity 0.15s; }
.motion-reduce-transform { transform: none !important; }
}
<!-- Tailwind -->
<div class="motion-safe:animate-fade motion-reduce:opacity-100"></div>
SSR / hydration safety
Rule: initial must always match what the server renders.
// WRONG — server renders opacity:1 but initial says 0 → hydration mismatch
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} />
// CORRECT — use AnimatePresence or defer to client mount
"use client"
const [mounted, setMounted] = useState(false)
useEffect(() => setMounted(true), [])
<motion.div
initial={{ opacity: mounted ? 0 : 1 }}
animate={{ opacity: 1 }}
/>
Code Examples
End-to-end: tokens + springs + accessibility + SSR guard
// components/fade-in-card.tsx
"use client"
import { useState, useEffect } from "react"
import { motion } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"
import { useSafeMotion } from "@/hooks/use-reduced-motion"
import { motionConfig } from "@/lib/motion-config"
interface FadeInCardProps {
children: React.ReactNode
delay?: number
}
export function FadeInCard({ children, delay = 0 }: FadeInCardProps) {
// SSR guard — initial must match server output (opacity: 1)
const [mounted, setMounted] = useState(false)
useEffect(() => setMounted(true), [])
// Accessibility — disables transform when reduced motion is preferred
const safeMotion = useSafeMotion(motionTokens.distance.md)
// Device gate — skip animation on low-end hardware
if (!motionConfig.shouldAnimate() || !mounted) {
return <div>{children}</div>
}
return (
<motion.div
initial={safeMotion.initial}
animate={safeMotion.animate}
exit={safeMotion.exit}
transition={{
...springs.gentle,
delay,
}}
whileHover={{ scale: motionTokens.scale.pop }}
whileTap={{ scale: motionTokens.scale.press }}
>
{children}
</motion.div>
)
}
Constraints / Non-Goals
This skill does not cover:
- UI component patterns (button, modal, stagger) → see
motion-patterns - Drag, gestures, SVG, text animations, custom hooks → see
motion-advanced - CSS-only animations or Tailwind
animate-*classes withoutmotion/react - Third-party animation libraries (GSAP, anime.js, etc.)
- Motion design decisions (when to animate, what to emphasize) — that is a design concern, not a code constraint
Anti-Patterns
| Anti-pattern | Rule violated | Fix |
|---|---|---|
import { motion } from "framer-motion" | Rule 1 | Use motion/react |
initial={{ opacity: 0 }} on SSR component | Rule 2 | Add mount guard |
Skipping useReducedMotion check | Rule 3 | Use useSafeMotion hook |
animate={{ width: "100%" }} | Rule 4 | Use scaleX transform instead |
transition={{ duration: 0.4 }} inline | Rule 5 | Use motionTokens.duration.normal |
{ stiffness: 300, damping: 30 } inline | Rule 6 | Use springs.snappy |
Missing "use client" directive | Rule 7 | Add to top of file |
navigator.hardwareConcurrency at module level | Rule 8 | Wrap in typeof navigator !== "undefined" |
Related Skills
motion-patterns— consumes tokens and springs defined here to build button, modal, stagger, page transition, and scroll patterns. Does not redefine any values.motion-advanced— consumes tokens and springs defined here for drag, SVG, text, and gesture patterns. AddsuseAnimatesequences and custom hooks on top of this foundation.
Related skills
More from affaan-m/everything-claude-code and the wider catalog.

motion-patterns
Production-ready animation patterns for React/Next.js built on motion-foundations tokens and springs.

motion-ui
Production-ready UI motion system for React/Next.js with performance, accessibility, and usability focus.

mysql-patterns
MySQL and MariaDB schema, indexing, transaction, and connection-pool patterns for production backends.

nanoclaw-repl
Zero-dependency session-aware REPL for Claude Code with persistent markdown storage and dynamic skill loading.

nestjs-patterns
Production-grade NestJS patterns for modular TypeScript backends with validation, guards, and config.

netmiko-ssh-automation
Safe Python Netmiko patterns for read-only SSH collection, batch automation, TextFSM parsing, and guarded config changes.