motion-foundations

Motion tokens, spring presets, performance rules, device adaptation, accessibility enforcement, and SSR safety for React / Next.js using motion/react.…

INSTALLATION
npx skills add https://github.com/affaan-m/everything-claude-code --skill motion-foundations
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

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.

  • **Use motion/react only.** Never import from framer-motion. Never mix the two in the same tree.
  • **initial must match server output.** If the server renders opacity: 1, the initial prop must also be opacity: 1. No exceptions.
  • 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.
  • Never animate layout properties. width, height, top, left, margin, padding are banned from animate. Use transform and opacity only.
  • **All token values come from motionTokens.** Hardcoded durations and easings in component files are forbidden.
  • **All spring configs come from the springs map.** Inline stiffness/damping values are forbidden.
  • **"use client" is required** on every file that imports from motion/react.
  • **Never read window or navigator at module level.** Always guard with typeof 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:

  • 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" &#x26;&#x26;

      window.matchMedia("(prefers-reduced-motion: reduce)").matches

    )

  },

  shouldAnimate({ essential = false } = {}) {

    if (this.prefersReduced()) return false

    if (!essential &#x26;&#x26; 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 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-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. Adds useAnimate sequences and custom hooks on top of this foundation.
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card