Claude Code for react-spring: Physics-Based UI Animations — Claude Skills 360 Blog
Blog / Frontend / Claude Code for react-spring: Physics-Based UI Animations
Frontend

Claude Code for react-spring: Physics-Based UI Animations

Published: May 1, 2027
Read time: 7 min read
By: Claude Skills 360

react-spring is a physics-based animation library for React — useSpring({ opacity: 1, transform: "translateY(0px)" }) returns animated values. animated.div renders the animated styles. config.wobbly gives a bouncy spring feel; config.gentle is smooth. useSprings(count, items.map(...)) animates multiple independent springs. useTrail(count, config) staggers animations across a list with the last spring driving the first. useTransition(items, { from, enter, leave }) handles mount and unmount animations. useChain([springRef, trailRef], [0, 0.3]) sequences animations. interpolate maps spring values to complex CSS. @use-gesture/react binds drag, hover, and scroll gestures to spring targets with useDrag. useSpringRef() creates an imperative handle for sequenced chains. Claude Code generates react-spring page transitions, list animations, drag-to-dismiss cards, parallax hero sections, and staggered grid entrances.

CLAUDE.md for react-spring

## react-spring Stack
- Version: @react-spring/web >= 9.7, @use-gesture/react >= 10.3
- Basic: const [styles, api] = useSpring(() => ({ opacity: 0, y: 20, config: config.gentle }))
- Trigger: api.start({ opacity: 1, y: 0 }) — imperative API
- Render: <animated.div style={styles}>
- List: const springs = useSprings(items.length, items.map(item => ({ ... })))
- Trail: const trail = useTrail(items.length, { opacity: open ? 1 : 0, y: open ? 0 : 20 })
- Transition: useTransition(show, { from: { opacity: 0 }, enter: { opacity: 1 }, leave: { opacity: 0 } })
- Gesture: const bind = useDrag(({ active, movement: [mx], velocity: [vx] }) => api.start(...))
- Config: config.gentle | config.wobbly | config.stiff | config.slow | { tension: 200, friction: 20 }

Animated Components

// components/animation/SpringCard.tsx — useSpring with hover and click
"use client"
import { useSpring, animated, config } from "@react-spring/web"
import { useState } from "react"

interface SpringCardProps {
  title: string
  description: string
  onClick?: () => void
}

export function SpringCard({ title, description, onClick }: SpringCardProps) {
  const [hovered, setHovered] = useState(false)
  const [clicked, setClicked] = useState(false)

  const [styles, api] = useSpring(() => ({
    scale: 1,
    y: 0,
    shadow: 4,
    config: config.gentle,
  }))

  const handleMouseEnter = () => {
    setHovered(true)
    api.start({ scale: 1.03, y: -4, shadow: 16 })
  }

  const handleMouseLeave = () => {
    setHovered(false)
    api.start({ scale: 1, y: 0, shadow: 4 })
  }

  const handleClick = () => {
    setClicked(true)
    api.start({
      scale: 0.96,
      config: { tension: 400, friction: 10 },
      onRest: () => {
        api.start({ scale: 1.03, config: config.wobbly })
        setClicked(false)
        onClick?.()
      },
    })
  }

  return (
    <animated.div
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
      onClick={handleClick}
      style={{
        transform: styles.scale.to(s => `scale(${s})`).to(s => `${s} translateY(${styles.y.get()}px)`),
        boxShadow: styles.shadow.to(s => `0 ${s}px ${s * 3}px rgba(0,0,0,0.1)`),
        cursor: "pointer",
      }}
      className="rounded-2xl border bg-card p-6 select-none"
    >
      <h3 className="font-semibold text-lg">{title}</h3>
      <p className="text-muted-foreground text-sm mt-2">{description}</p>
    </animated.div>
  )
}

Trail List Animation

// components/animation/TrailList.tsx — staggered list entrance
"use client"
import { useTrail, animated, config } from "@react-spring/web"
import { useEffect, useState } from "react"

interface TrailListProps {
  items: { id: string; label: string; icon?: string }[]
}

export function TrailList({ items }: TrailListProps) {
  const [open, setOpen] = useState(false)

  useEffect(() => {
    // Trigger entrance after mount
    const t = setTimeout(() => setOpen(true), 100)
    return () => clearTimeout(t)
  }, [])

  const trail = useTrail(items.length, {
    opacity: open ? 1 : 0,
    x: open ? 0 : 30,
    scale: open ? 1 : 0.9,
    config: { tension: 280, friction: 22 },
    delay: 50,
  })

  return (
    <ul className="space-y-2">
      {trail.map((styles, index) => (
        <animated.li
          key={items[index].id}
          style={{
            opacity: styles.opacity,
            transform: styles.x.to(x => `translateX(${x}px) scale(${styles.scale.get()})`),
          }}
          className="flex items-center gap-3 p-3 rounded-xl bg-muted/50 border"
        >
          {items[index].icon && (
            <span className="text-xl">{items[index].icon}</span>
          )}
          <span className="font-medium text-sm">{items[index].label}</span>
        </animated.li>
      ))}
    </ul>
  )
}

Mount/Unmount Transitions

// components/animation/ToastNotification.tsx — enter/leave transitions
"use client"
import { useTransition, animated, config } from "@react-spring/web"
import { useState, useCallback, useEffect } from "react"

type ToastType = "success" | "error" | "info"

interface Toast {
  id: string
  message: string
  type: ToastType
}

const COLORS: Record<ToastType, string> = {
  success: "bg-green-500",
  error: "bg-red-500",
  info: "bg-blue-500",
}

export function useToasts() {
  const [toasts, setToasts] = useState<Toast[]>([])

  const addToast = useCallback((message: string, type: ToastType = "info") => {
    const id = crypto.randomUUID()
    setToasts(prev => [...prev, { id, message, type }])
    setTimeout(() => {
      setToasts(prev => prev.filter(t => t.id !== id))
    }, 3500)
  }, [])

  return { toasts, addToast }
}

export function ToastContainer({ toasts }: { toasts: Toast[] }) {
  const transitions = useTransition(toasts, {
    keys: toast => toast.id,
    from: { opacity: 0, x: 60, scale: 0.85 },
    enter: { opacity: 1, x: 0, scale: 1 },
    leave: { opacity: 0, x: 60, scale: 0.85 },
    config: config.stiff,
  })

  return (
    <div className="fixed bottom-4 right-4 z-50 flex flex-col gap-2 items-end">
      {transitions((styles, toast) => (
        <animated.div
          style={{
            opacity: styles.opacity,
            transform: styles.x.to(x => `translateX(${x}px) scale(${styles.scale.get()})`),
          }}
          className={`${COLORS[toast.type]} text-white px-4 py-2.5 rounded-xl shadow-xl text-sm font-medium max-w-xs`}
        >
          {toast.message}
        </animated.div>
      ))}
    </div>
  )
}

Drag Gesture with @use-gesture

// components/animation/DraggableCard.tsx — drag with velocity-based dismiss
"use client"
import { useSpring, animated } from "@react-spring/web"
import { useDrag } from "@use-gesture/react"
import { useState } from "react"

interface DraggableCardProps {
  children: React.ReactNode
  onDismiss?: () => void
}

export function DraggableCard({ children, onDismiss }: DraggableCardProps) {
  const [dismissed, setDismissed] = useState(false)

  const [{ x, rotate, opacity }, api] = useSpring(() => ({
    x: 0,
    rotate: 0,
    opacity: 1,
    config: { tension: 300, friction: 30 },
  }))

  const bind = useDrag(
    ({ active, movement: [mx], velocity: [vx], direction: [dx] }) => {
      const trigger = Math.abs(vx) > 0.3 || Math.abs(mx) > 150

      if (!active && trigger) {
        // Dismiss — fly out
        setDismissed(true)
        api.start({
          x: dx > 0 ? 600 : -600,
          rotate: dx * 30,
          opacity: 0,
          config: { tension: 200, friction: 20 },
          onRest: onDismiss,
        })
      } else {
        api.start({
          x: active ? mx : 0,
          rotate: active ? mx / 12 : 0,
          opacity: 1,
          immediate: (key) => active && key === "x",
        })
      }
    },
    { filterTaps: true },
  )

  if (dismissed) return null

  return (
    <animated.div
      {...bind()}
      style={{
        x,
        rotate,
        opacity,
        touchAction: "none",
        cursor: "grab",
        userSelect: "none",
      }}
      className="rounded-2xl border bg-card p-6 shadow-lg w-80"
    >
      {children}
    </animated.div>
  )
}

Interpolation and Parallax

// components/animation/ParallaxHero.tsx — scroll-driven parallax
"use client"
import { useSpring, animated } from "@react-spring/web"
import { useEffect } from "react"

export function ParallaxHero({ title, subtitle }: { title: string; subtitle: string }) {
  const [{ scrollY }, scrollApi] = useSpring(() => ({ scrollY: 0 }))

  useEffect(() => {
    const handleScroll = () => scrollApi.start({ scrollY: window.scrollY, immediate: true })
    window.addEventListener("scroll", handleScroll, { passive: true })
    return () => window.removeEventListener("scroll", handleScroll)
  }, [scrollApi])

  // Interpolate scroll → parallax transforms
  const bgTransform = scrollY.to(y => `translateY(${y * 0.4}px)`)
  const headingTransform = scrollY.to(y => `translateY(${y * 0.15}px)`)
  const opacity = scrollY.to([0, 400], [1, 0], "clamp")

  return (
    <div className="relative h-screen overflow-hidden flex items-center justify-center">
      {/* Background parallax layer */}
      <animated.div
        style={{ transform: bgTransform }}
        className="absolute inset-0 -top-20 bg-gradient-to-br from-violet-600 via-purple-600 to-blue-700"
      />

      {/* Content layer */}
      <animated.div
        style={{ transform: headingTransform, opacity }}
        className="relative z-10 text-center text-white px-6"
      >
        <h1 className="text-5xl font-bold tracking-tight mb-4">{title}</h1>
        <p className="text-xl text-white/80 max-w-2xl mx-auto">{subtitle}</p>
      </animated.div>
    </div>
  )
}

For the Framer Motion alternative when a more feature-complete animation library with layout animations (layout prop), AnimatePresence for exit animations, MotionConfig, variants, and first-class Next.js/App Router support is needed — Framer Motion has more built-in defaults and better documentation for beginners, while react-spring excels at physics-based spring feel and gesture integration, see the Framer Motion guide. For the CSS transitions alternative when simple hover and enter/exit animations without a library are sufficient — Tailwind’s transition-all, duration-300, and ease-in-out utilities handle basic cases with zero bundle cost, see the Tailwind animation guide. The Claude Skills 360 bundle includes react-spring skill sets covering spring animations, transitions, and gesture interactions. Start with the free tier to try physics animation generation.

Keep Reading

Frontend

Claude Code for Chart.js Advanced: Custom Plugins and Mixed Charts

Advanced Chart.js patterns with Claude Code — chart.register() for tree-shaking, mixed chart types combining bar and line, custom plugin API with beforeDraw and afterDatasetsDraw hooks, ScriptableContext for computed colors, ChartDataLabels plugin for value labels, chartjs-plugin-zoom for pan and zoom, custom gradient fills via ctx.createLinearGradient, ChartJS annotation plugin for threshold lines, streaming data with chartjs-plugin-streaming, and react-chartjs-2 with useRef and chart instance.

6 min read Jun 27, 2027
Frontend

Claude Code for Nivo: Rich SVG and Canvas Charts

Build rich data visualizations with Nivo and Claude Code — ResponsiveLine and ResponsiveBar for adaptive charts, ResponsiveHeatMap for matrix data, ResponsiveTreeMap for hierarchal data, ResponsiveSunburst for nested proportions, ResponsiveChord for relationship diagrams, ResponsiveCalendar for activity heat maps, ResponsiveNetwork for force graphs, NivoTheme for consistent styling, tooltip customization with sliceTooltip, and motion config for spring animations.

6 min read Jun 26, 2027
Frontend

Claude Code for Victory Charts: React Native and Web Charts

Build cross-platform charts with Victory and Claude Code — VictoryChart, VictoryLine, VictoryBar, and VictoryScatter for web and React Native, VictoryPie for donut charts, VictoryArea for stacked areas, VictoryAxis for custom axes, VictoryTooltip and VictoryVoronoiContainer for hover tooltips, VictoryBrushContainer for range selection, VictoryZoomContainer for pan and zoom, VictoryLegend for series labels, custom theme with VictoryTheme, and VictoryStack for grouped bars.

6 min read Jun 25, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free