Lottie is a vector-based animation format stored as JSON. Developed by Airbnb, it allows animations created in After Effects to be played efficiently in web and mobile applications.
Compared to traditional GIF or video files, Lottie animations are much lighter, sharp at every screen size(because they're vectors), and runtime-modifiable (color, speed, stopping), which gives great design flexibility.
Lottie isn't suitable for very complex 3D effects or video content. In such cases video or WebGL is the better choice.
The most practical source for ready-made animations is lottiefiles.com. It hosts thousands of free animations. If you want to create your own, you can use After Effects with the Bodymovin plugin.
lottiefiles.com and create a free accountpublic/lotties/loading.json or src/assets/lotties/)data.json file in your projectNote: Watch the JSON file size when downloading. Files larger than 200 KB may cause performance issues; consider simplifying layers or keyframes.
The most popular library in React projects is lottie-react. It's lightweight, modern, and TypeScript-friendly.
npm install lottie-reactWith Yarn:
yarn add lottie-reactCreate a component and import the JSON you downloaded. In Next.js App Router this component must be marked "use client" because Lottie is a browser-only library.
"use client";
import Lottie from "lottie-react";
import animationData from "@/assets/lotties/loading.json";
export default function LoadingAnimation() {
return (
<Lottie
animationData={animationData}
loop
autoplay
style={{ width: 200, height: 200 }}
/>
);
}This project follows the same approach. There's a shared wrapper called CloudAnimation; you can pass any JSON via props and reuse it everywhere:
"use client";
import Lottie from "lottie-react";
export default function CloudAnimation({
file,
className,
}: {
file?: object;
className: string;
}) {
return <Lottie animationData={file} loop className={className} />;
}lottie-react exposes many props to control playback behavior.
autoplay — should it start automatically when the page loads? (default: true)loop — should it repeat indefinitely? (default: false)<Lottie animationData={data} autoplay={true} loop={false} />To control playback speed, use lottieRef and call setSpeed:
"use client";
import Lottie, { LottieRefCurrentProps } from "lottie-react";
import { useRef } from "react";
import data from "@/assets/lotties/wave.json";
export default function FastWave() {
const lottieRef = useRef<LottieRefCurrentProps>(null);
return (
<Lottie
lottieRef={lottieRef}
animationData={data}
onDOMLoaded={() => lottieRef.current?.setSpeed(2)}
loop
/>
);
}<button onClick={() => lottieRef.current?.play()}>Play</button>
<button onClick={() => lottieRef.current?.pause()}>Pause</button>
<button onClick={() => lottieRef.current?.stop()}>Stop</button>One of the most effective use cases is binding an animation to user interaction. Two classic patterns: play on hover and play with scroll.
"use client";
import Lottie, { LottieRefCurrentProps } from "lottie-react";
import { useRef } from "react";
import data from "@/assets/lotties/heart.json";
export default function HeartButton() {
const lottieRef = useRef<LottieRefCurrentProps>(null);
return (
<div
onMouseEnter={() => lottieRef.current?.play()}
onMouseLeave={() => lottieRef.current?.stop()}
>
<Lottie
lottieRef={lottieRef}
animationData={data}
autoplay={false}
loop={false}
style={{ width: 80, height: 80 }}
/>
</div>
);
}To map a scroll percentage to an animation frame use goToAndStop:
"use client";
import Lottie, { LottieRefCurrentProps } from "lottie-react";
import { useEffect, useRef } from "react";
import data from "@/assets/lotties/scroll.json";
export default function ScrollAnimation() {
const lottieRef = useRef<LottieRefCurrentProps>(null);
useEffect(() => {
const totalFrames = 60;
const onScroll = () => {
const ratio = window.scrollY / (document.body.scrollHeight - window.innerHeight);
const frame = Math.floor(ratio * totalFrames);
lottieRef.current?.goToAndStop(frame, true);
};
window.addEventListener("scroll", onScroll);
return () => window.removeEventListener("scroll", onScroll);
}, []);
return <Lottie lottieRef={lottieRef} animationData={data} autoplay={false} loop={false} />;
}The Lottie library uses browser APIs. When using it with SSR in the Next.js App Router, two things matter: hydration consistency and bundle size.
To render the Lottie component only on the client, use dynamic with ssr: false:
import dynamic from "next/dynamic";
const HeartButton = dynamic(() => import("./HeartButton"), {
ssr: false,
loading: () => <div style={{ width: 80, height: 80 }} />,
});Not loading the animation until it's in view significantly improves performance. Use IntersectionObserver:
"use client";
import { useEffect, useRef, useState } from "react";
import Lottie from "lottie-react";
export default function LazyLottie({ data }: { data: object }) {
const ref = useRef<HTMLDivElement>(null);
const [visible, setVisible] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => entry.isIntersecting && setVisible(true),
{ threshold: 0.2 }
);
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, []);
return (
<div ref={ref} style={{ minHeight: 200 }}>
{visible && <Lottie animationData={data} loop />}
</div>
);
}lottiefiles.com/tools/json-editor to remove unnecessary layersdotLottie formatOn mobile use lottie-react-native. It offers native performance on both iOS and Android.
npm install lottie-react-native lottie-ios
# For iOS
cd ios && pod installimport LottieView from "lottie-react-native";
export default function Loading() {
return (
<LottieView
source={require("./assets/loading.json")}
autoPlay
loop
style={{ width: 200, height: 200 }}
/>
);
}import LottieView from "lottie-react-native";
import { useRef } from "react";
import { Pressable } from "react-native";
export default function Heart() {
const lottieRef = useRef<LottieView>(null);
return (
<Pressable onPress={() => lottieRef.current?.play()}>
<LottieView
ref={lottieRef}
source={require("./assets/heart.json")}
autoPlay={false}
loop={false}
style={{ width: 80, height: 80 }}
/>
</Pressable>
);
}Note: If you use Expo, expo install lottie-react-nativeinstalls the right version automatically and you don't need to run pod install.
If the downloaded animation's color doesn't match your design, you have two options: edit it in After Effects and re-export, or modify the JSON manually.
Lottie JSON stores colors as RGBA arrays in the 0..1 range. For example, deep blue looks like:
"k": [0.137, 0.412, 0.745, 1]0.137 * 255 ≈ 35, 0.412 * 255 ≈ 105, 0.745 * 255 ≈ 190. So rgb(35, 105, 190). Replace these values to change the color.
The lottiefiles.com/tools/color-picker tool lets you upload a JSON file and replace all colors at once via a visual interface.
Because Lottie is vector based, it stays sharp at any size you set via width and height. For responsive sizing, just use CSS:
<Lottie
animationData={data}
loop
className="w-full max-w-md aspect-square"
/>Some users (vestibular conditions, attention sensitivity) enable the "reduce motion" setting. Respect their preference by checking the prefers-reduced-motion media query and disabling the animation:
"use client";
import Lottie from "lottie-react";
import { useEffect, useState } from "react";
import data from "@/assets/lotties/wave.json";
export default function AccessibleLottie() {
const [reduced, setReduced] = useState(false);
useEffect(() => {
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
setReduced(mq.matches);
const handler = (e: MediaQueryListEvent) => setReduced(e.matches);
mq.addEventListener("change", handler);
return () => mq.removeEventListener("change", handler);
}, []);
if (reduced) return <div aria-hidden="true" />;
return <Lottie animationData={data} loop />;
}Symptom:"Hydration failed" in the console.
Fix: Load the component with dynamic and ssr: false.
Symptom: Nothing renders on the page.
animationData, or a URL? For a URL, use the path prop insteadFix:
preserveAspectRatio: "xMidYMid meet" to rendererSettingswill-change: transform in CSSFix: Switch to dotLottie format. The same animation can be up to 90% smaller:
npm install @dotlottie/react-playerimport { DotLottiePlayer } from "@dotlottie/react-player";
<DotLottiePlayer src="/animations/wave.lottie" autoplay loop />With this guide you can now install Lottie animations in your project, control them, and use them with attention to performance and accessibility.