motion-foundations
affaan-m/ECC
Bewegungstoken, Feder-Voreinstellungen, Performance-Regeln, Geräteanpassung, Durchsetzung der Barrierefreiheit und SSR-Sicherheit für React/Next.js unter Verwendung von motion/react. Die Grundlagen – alle weiteren Motion-Kenntnisse bauen darauf auf.
...Alle erweiternGrundlagen des Bewegungssystems
Die Basisebene des Bewegungssystems. Definiert alle Werte, Einschränkungen und
Regeln, die nachgelagerte Fähigkeiten (motion-patterns, motion-advanced) erben.
Laden Sie diese Funktion, bevor jegliche Animationsarbeiten beginnen.
Wann aktivieren?
- Beim Starten einer animierten Komponente von Grund auf
- Einrichten von Tokens, Feder-Voreinstellungen oder Easing-Werten
- Implementierung
prefers-reduced-motionUnterstützung - Fehlerbehebung bei Hydration-Abweichungen von den Anfangszuständen der Animation
- Prüfen, ob eine Animation überhaupt vorhanden sein sollte
Ergebnisse
Diese Funktion liefert:
- Ein gemeinsames
motionTokensObjekt (Dauer, Beschleunigung, Entfernung, Skalierung) - Eine gemeinsam genutzte
springsgemeinsame Voreinstellungszuordnung (5 benannte Konfigurationen) - Ein
shouldAnimate()von allen Komponenten verwendetes Gate - Barrierefreiheitskonforme Standard-Animationen über
useReducedMotion - SSR-sichere Anfangszustände ohne Hydration-Warnungen
Grundsätze
Eine Bewegung muss mindestens eine der folgenden Bedingungen erfüllen oder sie muss entfernt werden:
- Die Aufmerksamkeit lenken
- Den Status vermitteln
- Räumliche Kontinuität wahren
Reaktionsfähigkeit hat immer Vorrang vor Laufruhe. Eine Animation mit 60 fps, die zu Eingabeverzögerungen führt, ist schlechter als gar keine Animation.
Regeln
Diese sind nicht verhandelbar. Sie gelten für jede Komponente im System.
- Verwenden Sie ausschließlich „
motion/react“. Importieren Sie niemals ausframer-motion. Mischen Sie niemals beides in derselben Baumstruktur. initialmuss mit der Serverausgabe übereinstimmen. Wenn der Serveropacity: 1, muss dasinitialProp ebenfallsopacity: 1. Keine Ausnahmen.- Reduzierte Bewegung hat Vorrang vor allem anderen. Wenn
useReducedMotion()gibttrueoderprefersReducedisttrue, werden alle Transformationen deaktiviert. Als einzige Ausnahmeregelung sind Fade-Effekte, die ausschließlich die Deckkraft betreffen und ≤ 0,2 s dauern, zulässig. - Layout-Eigenschaften dürfen niemals animiert werden.
width,height,top,left,margin,paddingsind inanimate. Verwenden Sietransformundopacity. - Alle Token-Werte stammen aus „
motionTokens“. Fest codierte Dauerangaben und Beschleunigungsverläufe in Komponentendateien sind verboten. - Alle Spring-Konfigurationen stammen aus der Map „
springs“. Inline-stiffness/dampingWerte sind verboten. "use client"ist in jeder Datei erforderlich, die ausmotion/react.- Lesen Sie niemals
windowodernavigatorauf Modulebene. Verwenden Sie stetstypeof window !== "undefined".
Leitfaden zur Entscheidungsfindung
Auswahl einer Dauer
Auswahl einer Feder
Wann sollte die Animation vollständig deaktiviert werden?
Deaktivieren (so, dass shouldAnimate() Rückkehr false) in folgenden Fällen:
prefersReducedisttrueisLowEndisttrueund die Animation nicht wesentlich ist- Das Element befindet sich außerhalb des Bildschirms und wird niemals in den Ansichtsbereich gelangen
- Die Animation ist rein dekorativ und hat keinen UX-Zweck
Kernkonzepte
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 },
}
Laufzeit-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
},
}
Barrierefreiheit
Prioritätsreihenfolge (von der höchsten zur niedrigsten):
prefers-reduced-motion: reduce— deaktiviert alle Transformationen, begrenzt Opazitätsübergänge auf ≤ 0,2 s- Erkennung von Geräten mit geringer Leistung — verkürzt die Dauer, entfernt nicht wesentliche Animationen
- Designpräferenz — alles andere
Bewegungen müssen sanft abklingen. Sie dürfen niemals abrupt verschwinden, sodass es zu Layoutverschiebungen kommt oder die Orientierung beeinträchtigt wird.
// 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; }
}
<div class="motion-safe:animate-fade motion-reduce:opacity-100">div>
SSR-/Hydration-Sicherheit
Regel: „initial“ muss immer mit dem übereinstimmen, was der Server rendert.
// WRONG — server renders opacity:1 but initial says 0 → hydration mismatch
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), [])
div
initial={{ opacity: mounted ? 0 : 1 }}
animate={{ opacity: 1 }}
/>
Code-Beispiele
End-to-End: Tokens + Springs + Barrierefreiheit + 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>
)
}
Einschränkungen / Nicht-Ziele
Diese Fertigkeit umfasst nicht:
- UI-Komponentenmuster (Schaltflächen, Modals, Stagger) → siehe
motion-patterns - Drag, Gesten, SVG, Textanimationen, benutzerdefinierte Hooks → siehe
motion-advanced - Reine CSS-Animationen oder Tailwind-
animate-*Klassen ohnemotion/react - Animationsbibliotheken von Drittanbietern (GSAP, anime.js usw.)
- Entscheidungen zum Motion Design (wann animiert werden soll, was hervorgehoben werden soll) – das ist eine gestalterische Frage, keine Einschränkung durch den Code
Anti-Muster
Verwandte Fähigkeiten
motion-patterns— verwendet die hier definierten Tokens und Springs, um Muster für Schaltflächen, Modal-Fenster, Stagger-Effekte, Seitenübergänge und Scroll-Effekte zu erstellen. Es werden keine Werte neu definiert.motion-advanced— Verwendet die hier definierten Tokens und Springs für Drag-, SVG-, Text- und Gestenmuster. FügtuseAnimateSequenzen und benutzerdefinierte Hooks auf dieser Grundlage hinzu.
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
Choosing a spring
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
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.
motion-foundations installieren
Laden Sie die Skill-Dateien herunter und entpacken Sie sie in Ihr Verzeichnis „.claude/skills/“.
ZIP herunterladenKlonen Sie das Repository und kopieren Sie die Skill-Dateien in Ihr Projekt.
git clone https://github.com/affaan-m/ECC/tree/main/skills/motion-foundations # Copy the skill folder to .claude/skills/ or .codex/skills/
Kopieren





Heim
