Limited offer — lifetime access:$99$79
Articlereactlottieanimated iconstutoriallottie-react

How to Use Lottie in React — Complete Guide with Code

How to use Lottie animations in React with lottie-react and lottie-web. Covers installation, JSON import, play/pause control, hover triggers, and common pitfalls.

Lottie animations are JSON files exported from After Effects (or purpose-built tools). In React, you render them with the lottie-react package — a thin wrapper around the lottie-web runtime. This guide covers the complete setup, from install to production-ready patterns.

Download Lottie JSON files at Unicorn Icons — 100 are free, no account needed.

lottie-react vs lottie-web — which to use?

lottie-web is the core rendering library. lottie-react wraps it with a declarative component API.

Use lottie-react when:

  • You want props-driven control (loop, autoplay, speed)
  • You have a standard React component tree

Use lottie-web directly when:

  • You need fine-grained imperative control
  • You're integrating with a non-React UI layer
  • You want to minimize dependencies

This guide covers lottie-react. For lottie-web direct usage, see the HTML guide.

Setup

1. Install

bash
copy
npm install lottie-react
# or
yarn add lottie-react

lottie-react includes lottie-web as a peer dependency — no separate install needed.

2. Basic usage

tsx
copy
import Lottie from "lottie-react";
import arrowAnimation from "./arrow.json";

export default function ArrowIcon() {
  return (
    <Lottie
      animationData={arrowAnimation}
      loop={true}
      style={{ width: 32, height: 32 }}
    />
  );
}

Three things to note:

  • animationData takes the parsed JSON object — not a URL string
  • loop={true} repeats indefinitely; loop={false} plays once
  • Always set an explicit width/height — the canvas needs dimensions to render

3. Controlling playback

For hover animations, button toggles, or scroll triggers, use lottieRef:

tsx
copy
import Lottie, { LottieRefCurrentProps } from "lottie-react";
import { useRef } from "react";
import checkAnimation from "./check.json";

export default function CheckIcon() {
  const lottieRef = useRef<LottieRefCurrentProps>(null);

  return (
    <button
      onMouseEnter={() => lottieRef.current?.play()}
      onMouseLeave={() => lottieRef.current?.stop()}
    >
      <Lottie
        lottieRef={lottieRef}
        animationData={checkAnimation}
        loop={false}
        autoplay={false}
        style={{ width: 32, height: 32 }}
      />
    </button>
  );
}

lottieRef.current exposes: play(), pause(), stop(), setSpeed(n), setDirection(1 | -1), goToAndPlay(frame), goToAndStop(frame).

4. Playing once and stopping at last frame

tsx
copy
<Lottie
  animationData={animation}
  loop={false}
  onComplete={() => lottieRef.current?.goToAndStop(
    lottieRef.current.getDuration(true) - 1,
    true
  )}
/>

getDuration(true) returns total frames. goToAndStop(frame, true) holds the last frame so the icon doesn't snap back to frame 0.


Common pitfalls

Pitfall 1: Animation JSON changes on every render

tsx
copy
// ❌ Creates a new object reference every render
<Lottie animationData={{ v: "5.9.0", ... }} />

// ✅ Import at module level
import animationData from "./animation.json";
<Lottie animationData={animationData} />

Inline JSON objects cause the animation to restart on every parent re-render. Always import JSON files at module level or define them outside the component.

Pitfall 2: No width/height → invisible or wrong size

tsx
copy
// ❌ Canvas has no dimensions
<Lottie animationData={animation} />

// ✅ Always set explicit dimensions
<Lottie
  animationData={animation}
  style={{ width: 32, height: 32 }}
/>
// or with Tailwind:
<div className="w-8 h-8">
  <Lottie animationData={animation} />
</div>

Pitfall 3: Forgetting autoplay={false} for interactive icons

tsx
copy
// ❌ Animation plays on mount and the ref.play() call on hover is out of sync
<Lottie lottieRef={ref} animationData={animation} />

// ✅
<Lottie lottieRef={ref} animationData={animation} autoplay={false} />

Pitfall 4: SSR in Next.js (document is not defined)

lottie-web uses document and window — it can't run server-side. In Next.js:

tsx
copy
// components/LottieIcon.tsx
"use client";
import Lottie from "lottie-react";
// ... rest of component

Or with dynamic import:

tsx
copy
import dynamic from "next/dynamic";
const Lottie = dynamic(() => import("lottie-react"), { ssr: false });

See the Next.js animated icons guide for a full walkthrough.


Performance

lottie-web is ~250KB minified. For apps with many icons, strategies to keep bundle size under control:

1. Load JSON on demand:

tsx
copy
import { useState, useEffect } from "react";
import Lottie from "lottie-react";

export default function LazyLottie({ src }: { src: string }) {
  const [data, setData] = useState<object | null>(null);

  useEffect(() => {
    fetch(src).then(r => r.json()).then(setData);
  }, [src]);

  if (!data) return <div style={{ width: 32, height: 32 }} />;
  return <Lottie animationData={data} style={{ width: 32, height: 32 }} />;
}

2. Use renderer: "svg" for icons (default is "svg" — no change needed).

3. Avoid rendering off-screen animations. Use an Intersection Observer to pause() icons that aren't visible.


Full reusable component

A production-ready LottieIcon component:

tsx
copy
"use client";

import Lottie, { LottieRefCurrentProps } from "lottie-react";
import { useRef, useCallback } from "react";

interface LottieIconProps {
  animationData: object;
  size?: number;
  playOnHover?: boolean;
  loop?: boolean;
  className?: string;
}

export default function LottieIcon({
  animationData,
  size = 32,
  playOnHover = false,
  loop = true,
  className,
}: LottieIconProps) {
  const lottieRef = useRef<LottieRefCurrentProps>(null);

  const handleMouseEnter = useCallback(() => {
    if (playOnHover) lottieRef.current?.play();
  }, [playOnHover]);

  const handleMouseLeave = useCallback(() => {
    if (playOnHover) lottieRef.current?.stop();
  }, [playOnHover]);

  return (
    <div
      style={{ width: size, height: size }}
      className={className}
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
    >
      <Lottie
        lottieRef={lottieRef}
        animationData={animationData}
        loop={loop}
        autoplay={!playOnHover}
      />
    </div>
  );
}

Usage:

tsx
copy
import LottieIcon from "@/components/LottieIcon";
import arrowData from "@/public/icons/arrow.json";

// Looping animation
<LottieIcon animationData={arrowData} />

// Plays only on hover
<LottieIcon animationData={arrowData} playOnHover size={24} />

Next steps