PluginBench
Skill
Pass
Audit score 90

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
Prerequisites
  • motion/react package installed (not framer-motion)
  • React 18+ with Next.js or similar SSR-capable framework
  • TypeScript recommended for token type safety
Claude Code
Cursor
Windsurf
Cline

How to use motion-foundations

  1. 1.Create lib/motion-tokens.ts with motionTokens object and springs preset map
  2. 2.Create lib/motion-config.ts with motionConfig object containing isLowEnd(), prefersReduced(), shouldAnimate(), and duration() methods
  3. 3.Create hooks/use-reduced-motion.tsx hook that wraps useReducedMotion() and returns safe initial/animate/exit states
  4. 4.Add 'use client' directive to all files importing from motion/react
  5. 5.Use motionTokens and springs in component files instead of hardcoded values
  6. 6.Guard window/navigator access with typeof window !== 'undefined' checks
  7. 7.Ensure initial prop always matches server-rendered output to prevent hydration mismatches

Use cases

Good for
  • 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
Who it's for
  • 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

Which library should I import from: motion/react or framer-motion?

Always use motion/react only. Never import from framer-motion or mix the two in the same tree. This is a non-negotiable rule.

What should I do when prefers-reduced-motion is enabled?

Disable all transforms and use opacity-only fades at ≤0.2s duration as the only permitted fallback. The useReducedMotion hook handles this automatically.

How do I avoid SSR hydration mismatches?

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.

When should I disable animation entirely?

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.

Can I use hardcoded duration and easing values in my components?

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

TokenUse when
instantTooltip show/hide, focus ring, badge update
fastButton feedback, icon swap, chip toggle
normalModal open, card expand, page element enter
slowHero entrance, full-page transition
crawlDeliberate storytelling; use sparingly

Choosing a spring

PresetUse when
snappyDefault UI — buttons, chips, nav items
gentleCards, modals, panels landing softly
bouncyPlayful moments — empty states, onboarding
instantTooltips, popovers, dropdowns
releaseDrag release — natural physics feel

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

Anti-patternRule violatedFix
import { motion } from "framer-motion"Rule 1Use motion/react
initial={{ opacity: 0 }} on SSR componentRule 2Add mount guard
Skipping useReducedMotion checkRule 3Use useSafeMotion hook
animate={{ width: "100%" }}Rule 4Use scaleX transform instead
transition={{ duration: 0.4 }} inlineRule 5Use motionTokens.duration.normal
{ stiffness: 300, damping: 30 } inlineRule 6Use springs.snappy
Missing "use client" directiveRule 7Add to top of file
navigator.hardwareConcurrency at module levelRule 8Wrap 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. Adds useAnimate sequences and custom hooks on top of this foundation.