Claude Code for Lottie: JSON-Based Vector Animations — Claude Skills 360 Blog
Blog / Frontend / Claude Code for Lottie: JSON-Based Vector Animations
Frontend

Claude Code for Lottie: JSON-Based Vector Animations

Published: May 4, 2027
Read time: 6 min read
By: Claude Skills 360

Lottie renders After Effects animations as JSON in the browser — lottie-react wraps lottie-web with a <Lottie> component and animationData prop for bundled JSON. lottieRef.current.play(), .pause(), .stop(), .setSpeed(2), and .goToAndPlay(frame, true) control playback imperatively. loop={false} plays once; autoplay={false} waits for .play(). onComplete and onLoopComplete are event callbacks. DotLottie is the newer .lottie binary format (50% smaller than JSON) via @lottiefiles/dotlottie-react. Segment playback with playSegments([[0, 30], [60, 90]]) plays named states. lottie.loadAnimation({ container, renderer: "svg", animationData }) is the vanilla API without React. lottie-react-native uses the same JSON files for mobile. Claude Code generates Lottie loading skeletons, success/error state animations, button micro-interactions, and onboarding illustrations.

CLAUDE.md for Lottie

## Lottie Stack
- Version: lottie-react >= 2.4, @lottiefiles/dotlottie-react >= 0.12
- JSON: import animationData from "@/animations/success.json" — bundled
- URL: animationData fetched at runtime for large files
- Ref: const lottieRef = useRef<LottieRefCurrentProps>(null)
- Controls: lottieRef.current.play() | .pause() | .stop() | .setSpeed(1.5)
- Events: onComplete, onLoopComplete, onEnterFrame({ currentTime, totalTime })
- DotLottie: <DotLottieReact src="/animations/confetti.lottie" loop autoplay />
- Segments: lottieRef.current.playSegments([startFrame, endFrame], true)

Lottie React Component

// components/animation/LottiePlayer.tsx — reusable Lottie component
"use client"
import Lottie, { type LottieRefCurrentProps } from "lottie-react"
import { useRef, useEffect, useCallback, forwardRef } from "react"

interface LottiePlayerProps {
  animationData: object
  width?: number | string
  height?: number | string
  loop?: boolean
  autoplay?: boolean
  speed?: number
  className?: string
  onComplete?: () => void
  onLoopComplete?: () => void
  style?: React.CSSProperties
}

export interface LottiePlayerRef {
  play: () => void
  pause: () => void
  stop: () => void
  setSpeed: (speed: number) => void
  goToAndPlay: (frame: number) => void
  goToAndStop: (frame: number) => void
}

export const LottiePlayer = forwardRef<LottiePlayerRef, LottiePlayerProps>(
  function LottiePlayer(
    { animationData, width = "100%", height = "auto", loop = true, autoplay = true, speed = 1, className, onComplete, onLoopComplete, style },
    ref,
  ) {
    const lottieRef = useRef<LottieRefCurrentProps>(null)

    // Expose imperative controls via forwarded ref
    useEffect(() => {
      if (!ref || typeof ref !== "object" || !ref.current) return
      if (!lottieRef.current) return

      const controls = lottieRef.current
      ref.current.play = () => controls.play()
      ref.current.pause = () => controls.pause()
      ref.current.stop = () => controls.stop()
      ref.current.setSpeed = (s) => controls.setSpeed(s)
      ref.current.goToAndPlay = (frame) => controls.goToAndPlay(frame, true)
      ref.current.goToAndStop = (frame) => controls.goToAndStop(frame, true)
    }, [ref])

    useEffect(() => {
      lottieRef.current?.setSpeed(speed)
    }, [speed])

    return (
      <Lottie
        lottieRef={lottieRef}
        animationData={animationData}
        loop={loop}
        autoplay={autoplay}
        onComplete={onComplete}
        onLoopComplete={onLoopComplete}
        style={{ width, height, ...style }}
        className={className}
      />
    )
  },
)

State Machine Animations

// components/animation/StatusAnimation.tsx — multi-state Lottie
"use client"
import Lottie, { type LottieRefCurrentProps } from "lottie-react"
import { useRef, useEffect, useState } from "react"

// Import animation data
import loadingAnimation from "@/animations/loading-spinner.json"
import successAnimation from "@/animations/success-checkmark.json"
import errorAnimation from "@/animations/error-shake.json"

type AnimationState = "idle" | "loading" | "success" | "error"

const ANIMATIONS: Record<Exclude<AnimationState, "idle">, { data: object; loop: boolean }> = {
  loading: { data: loadingAnimation, loop: true },
  success: { data: successAnimation, loop: false },
  error: { data: errorAnimation, loop: false },
}

// Segment ranges within a single "state machine" Lottie file
const SEGMENTS = {
  idle: [0, 0] as [number, number],
  loading: [0, 60] as [number, number],
  success: [60, 120] as [number, number],
  error: [120, 180] as [number, number],
}

interface StatusAnimationProps {
  state: AnimationState
  size?: number
  onComplete?: () => void
}

export function StatusAnimation({ state, size = 80, onComplete }: StatusAnimationProps) {
  if (state === "idle") return <div style={{ width: size, height: size }} />

  const { data, loop } = ANIMATIONS[state]

  return (
    <Lottie
      animationData={data}
      loop={loop}
      autoplay={true}
      onComplete={onComplete}
      style={{ width: size, height: size }}
    />
  )
}

// Button with embedded loading/success animation
interface AnimatedButtonProps {
  onClick: () => Promise<void>
  children: React.ReactNode
  className?: string
}

export function AnimatedSubmitButton({ onClick, children, className }: AnimatedButtonProps) {
  const [state, setState] = useState<AnimationState>("idle")

  const handleClick = async () => {
    if (state !== "idle") return
    setState("loading")

    try {
      await onClick()
      setState("success")
      setTimeout(() => setState("idle"), 2000)
    } catch {
      setState("error")
      setTimeout(() => setState("idle"), 2500)
    }
  }

  const isIdle = state === "idle"

  return (
    <button
      onClick={handleClick}
      disabled={state !== "idle"}
      className={`relative flex items-center justify-center gap-2 ${className}`}
    >
      {!isIdle && (
        <StatusAnimation
          state={state}
          size={24}
          onComplete={state === "success" || state === "error" ? undefined : undefined}
        />
      )}
      <span className={state !== "idle" ? "sr-only" : ""}>{children}</span>
      {state === "loading" && <span>Processing...</span>}
      {state === "success" && <span>Done!</span>}
      {state === "error" && <span>Try again</span>}
    </button>
  )
}

DotLottie Player

// components/animation/DotLottieHero.tsx — .lottie binary format (smaller)
"use client"
import { DotLottieReact, type DotLottieRefProps } from "@lottiefiles/dotlottie-react"
import { useRef, useCallback } from "react"

interface HeroAnimationProps {
  src: string
  className?: string
}

export function HeroAnimation({ src, className }: HeroAnimationProps) {
  const dotLottieRef = useRef<DotLottieRefProps>(null)

  const handleMouseEnter = useCallback(() => {
    dotLottieRef.current?.dotLottie?.setSpeed(1.5)
  }, [])

  const handleMouseLeave = useCallback(() => {
    dotLottieRef.current?.dotLottie?.setSpeed(1)
  }, [])

  return (
    <div
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
      className={className}
    >
      <DotLottieReact
        ref={dotLottieRef}
        src={src}
        loop
        autoplay
        renderConfig={{ autoResize: true }}
      />
    </div>
  )
}

Lazy-Loaded Remote Animation

// components/animation/RemoteLottie.tsx — fetch JSON from CDN, lazy
"use client"
import { useState, useEffect } from "react"
import dynamic from "next/dynamic"

// Lazy-import lottie-react to avoid SSR issues
const Lottie = dynamic(() => import("lottie-react"), {
  ssr: false,
  loading: () => <div className="animate-pulse bg-muted rounded-full" />,
})

interface RemoteLottieProps {
  url: string
  width?: number
  height?: number
  loop?: boolean
}

export function RemoteLottie({ url, width = 200, height = 200, loop = true }: RemoteLottieProps) {
  const [animationData, setAnimationData] = useState<object | null>(null)

  useEffect(() => {
    let cancelled = false
    fetch(url)
      .then(r => r.json())
      .then(data => { if (!cancelled) setAnimationData(data) })
      .catch(console.error)

    return () => { cancelled = true }
  }, [url])

  if (!animationData) {
    return (
      <div
        className="animate-pulse bg-muted rounded-full"
        style={{ width, height }}
      />
    )
  }

  return (
    <Lottie
      animationData={animationData}
      loop={loop}
      autoplay
      style={{ width, height }}
    />
  )
}

For the react-spring alternative when physics-based spring animations driven by JavaScript state rather than pre-exported After Effects files are preferred — react-spring is code-authored and great for gesture-driven interactions, while Lottie plays designer-authored animations that require no coding but also cannot be dynamically driven by data, see the react-spring guide. For the CSS/Tailwind animation alternative when simple keyframe animations (bounce, pulse, spin, fade) without a library are sufficient — Tailwind’s animate-spin, animate-pulse, and custom @keyframes in tailwind.config handle icon spinners and skeleton loaders at zero bundle cost, see the Tailwind animation guide. The Claude Skills 360 bundle includes Lottie skill sets covering animation players, state machines, and DotLottie integration. Start with the free tier to try Lottie 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