option

motion-foundations

affaan-m/ECC affaan-m/ECC

Jetons de mouvement, préréglages de ressort, règles de performance, adaptation aux appareils, mise en œuvre de l'accessibilité et sécurité SSR pour React / Next.js à l'aide de motion/react. Couche de base — toutes les autres compétences en matière de mouvement reposent sur celle-ci.

...Développer tout
51
Heure mise à jour 29 juillet 2026

Principes fondamentaux du mouvement

La couche de base du système de mouvement. Définit toutes les valeurs, contraintes et règles dont héritent les compétences en aval (motion-patterns, motion-advanced) héritent. Chargez cette compétence avant de commencer tout travail d’animation.

Quand l'activer

  • Lorsque vous créez un composant animé à partir de zéro
  • Configurer des jetons, des préréglages de ressort ou des valeurs d’accélération/décélération
  • Mise en œuvre prefers-reduced-motion la prise en charge
  • Déboguer les incohérences d'hydratation par rapport aux états initiaux de l'animation
  • Évaluer si une animation doit réellement exister

Résultats

Cette compétence génère :

  • Un objet partagé motionTokens (durée, accélération, distance, échelle)
  • Une springs carte de préréglages partagée (5 configurations nommées)
  • Une shouldAnimate() porte utilisée par tous les composants
  • Paramètres d’animation par défaut conformes aux normes d’accessibilité via useReducedMotion
  • des états initiaux compatibles avec le SSR, sans aucun avertissement d'hydratation

Principes

Une animation doit remplir au moins l’une des conditions suivantes, sinon elle doit être supprimée :

  • Guider l’attention
  • Communiquer un état
  • Préserver la continuité spatiale

La réactivité prime toujours sur la fluidité. Une animation à 60 images par seconde qui entraîne un retard de réponse est pire que l'absence d'animation.

Règles

Elles sont non négociables. Elles s’appliquent à tous les composants du système.

  1. Utilisez uniquement motion/react. N’importez jamais depuis framer-motion. Ne mélangez jamais les deux dans la même arborescence.
  2. initial doit correspondre à la sortie du serveur. Si le serveur affiche opacity: 1, la initial propriété doit également être opacity: 1. Aucune exception.
  3. La réduction des mouvements prime sur tout le reste. Lorsque useReducedMotion() renvoie true ou prefersReduced est true, toutes les transformations sont désactivées. Les fondus basés uniquement sur l’opacité d’une durée ≤ 0,2 s constituent la seule solution de repli autorisée.
  4. N’animez jamais les propriétés de mise en page. width, height, top, left, margin, padding sont interdites dans animate. Utilisez transform et opacity uniquement.
  5. Toutes les valeurs des jetons proviennent de motionTokens. Les durées et les accélérations codées en dur dans les fichiers de composants sont interdites.
  6. Toutes les configurations Spring proviennent de la carte springs. Les stiffness/damping sont interdites.
  7. "use client" est obligatoire dans chaque fichier qui importe depuis motion/react.
  8. Ne lisez jamais window ou navigator au niveau du module. Utilisez toujours la protection typeof window !== "undefined".

Conseils de décision

Choix d'une durée

Choix d'un ressort

Quand désactiver complètement l’animation

Désactiver (rendre shouldAnimate() retour false) lorsque :

  • prefersReduced est true
  • isLowEnd est true et que l’animation n’est pas essentielle
  • L'élément est hors écran et n'apparaîtra jamais dans la fenêtre d'affichage
  • L’animation est purement décorative et n’a aucune utilité en termes d’expérience utilisateur

Concepts fondamentaux

Système de jetons

// 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 },
}

Indicateurs d'exécution

// 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
  },
}

Accessibilité

Ordre de priorité (du plus élevé au plus bas) :

  1. prefers-reduced-motion: reduce — désactive toutes les transformations, limite les transitions d’opacité à ≤ 0,2 s
  2. Détection des appareils bas de gamme — réduit la durée, supprime les animations non essentielles
  3. Préférences de conception — tout le reste

Le mouvement doit se dégrader en douceur. Il ne doit jamais disparaître brusquement d’une manière qui provoque un décalage de mise en page ou perturbe l’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; }
}

<div class="motion-safe:animate-fade motion-reduce:opacity-100">div>

Sécurité SSR / hydratation

Règle : l’initiale doit toujours correspondre à ce que le serveur affiche.

// 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 }}
/>

Exemples de code

De bout en bout : jetons + ressorts + accessibilité + garde SSR

// 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>
  )
}

Contraintes / Objectifs non visés

Cette compétence ne couvre pas :

  • Les modèles de composants d’interface utilisateur (bouton, modal, stagger) → voir motion-patterns
  • Glisser-déposer, gestes, SVG, animations de texte, hooks personnalisés → voir motion-advanced
  • Animations uniquement en CSS ou classes animate-* sans motion/react
  • bibliothèques d’animation tierces (GSAP, anime.js, etc.)
  • Décisions de conception du mouvement (quand animer, quoi mettre en valeur) — il s’agit d’une question de conception, et non d’une contrainte de code

Anti-modèles

Compétences associées

  • motion-patterns — utilise les jetons et les ressorts définis ici pour créer des modèles de boutons, de fenêtres modales, d’effets de décalage, de transitions de page et de défilement. Ne redéfinit aucune valeur.
  • motion-advanced — utilise les jetons et les ressorts définis ici pour les effets de glissement, SVG, de texte et de gestes. Ajoute useAnimate des séquences et des hooks personnalisés en complément de cette base.
Voir sur GitHub

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-motion support
  • Debugging hydration mismatches from animation initial states
  • Evaluating whether an animation should exist at all

Outputs

This skill produces:

  • A shared motionTokens object (duration, easing, distance, scale)
  • A shared springs preset 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.

  1. Use motion/react only. Never import from framer-motion. Never mix the two in the same tree.
  2. initial must match server output. If the server renders opacity: 1, the initial prop must also be opacity: 1. No exceptions.
  3. Reduced motion overrides everything. When useReducedMotion() returns true or prefersReduced is true, all transforms are disabled. Opacity-only fades at ≤ 0.2s are the only permitted fallback.
  4. Never animate layout properties. width, height, top, left, margin, padding are banned from animate. Use transform and opacity only.
  5. All token values come from motionTokens. Hardcoded durations and easings in component files are forbidden.
  6. All spring configs come from the springs map. Inline stiffness/damping values are forbidden.
  7. "use client" is required on every file that imports from motion/react.
  8. Never read window or navigator at module level. Always guard with typeof window !== "undefined".

Decision Guidance

Choosing a duration

Choosing a spring

When to disable animation entirely

Disable (make shouldAnimate() return false) when:

  • prefersReduced is true
  • isLowEnd is true and 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):

  1. prefers-reduced-motion: reduce — disables all transforms, limits opacity transitions to ≤ 0.2s
  2. Low-end device detection — reduces duration, removes non-essential animations
  3. 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 without motion/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. Adds useAnimate sequences and custom hooks on top of this foundation.

Tous les fichiers

1 fichiers
SKILL.md 9.4k
Voir

Installer motion-foundations

Téléchargez et décompressez les fichiers de compétences dans votre répertoire .claude/skills/.

Télécharger le ZIP

Clonez le dépôt et copiez les fichiers de compétence dans votre projet.

git clone https://github.com/affaan-m/ECC/tree/main/skills/motion-foundations # Copy the skill folder to .claude/skills/ or .codex/skills/

Copier Copier
Configuration rapide: Copiez le dossier de la compétence dans .claude/skills/ ; Claude la détectera automatiquement et l'utilisera.
Dépôt affaan-m/ECC

Compétences similaires

web-search
Heure mise à jour 29 juin 2026
computer-use
Heure mise à jour 29 juillet 2026
webapp-testing
Heure mise à jour 29 juin 2026
lark-base
Heure mise à jour 5 juillet 2026
OR