Lottie Animations in React 2026: @lottiefiles/react, DotLottie Setup
How to add Lottie animations to React in 2026 — comparing @lottiefiles/react-lottie-player, lottie-react, and DotLottie with real setup code and gotchas.
Why Lottie Still Makes Sense in 2026
Lottie has been around since Airbnb open-sourced it in 2017, and honestly, it's held up better than most animation libraries from that era. The core idea is simple: you export an After Effects animation as JSON, ship that JSON file, and a tiny renderer plays it at any size without degradation. No GIFs, no bloated video files, no hand-rolling CSS keyframes for something a motion designer spent two weeks on.
The ecosystem fractured a bit over 2023–2024, which is probably why you're here. There's the original lottie-web, the official lottie-react wrapper, @lottiefiles/react-lottie-player, and the newer DotLottie format (.lottie instead of .json) that bundles everything into a compressed archive. That said, each one has a distinct use case, and picking the wrong one will cost you bundle size or flexibility.
For UI polish — loading spinners, success states, empty state illustrations — Lottie is still one of the fastest ways to ship polished animation without fighting CSS. If you're building something with distinct visual personality, like the glassmorphism components or a cyberpunk-themed dashboard, animated icons via Lottie can reinforce the aesthetic without a designer having to hand-off 40 individual SVG animations.
Worth noting: the DotLottie format is roughly 40–60% smaller than the equivalent JSON because it's a ZIP-based container with built-in compression. If you're shipping more than 3–4 animations per page, that difference adds up fast.
Picking Your Package: lottie-react vs @lottiefiles/react-lottie-player vs DotLottie
Three realistic options in 2026 and they're not interchangeable. `lottie-react` (npm i lottie-react) is the community wrapper around lottie-web. It's the most battle-tested, closest to the metal, and gives you direct access to the underlying lottie-web animation instance for programmatic control. If you need to call play(), pause(), goToAndStop() from outside the component, this is your pick.
`@lottiefiles/react-lottie-player` is LottieFiles' own React component. It's more opinionated, ships with a controls UI baked in, and makes it embarrassingly easy to drop an animation with playback controls without wiring anything up yourself. The downside is it adds about 15–20 KB extra compared to lottie-react because of that controls layer. Fine for marketing pages, overkill for a loading spinner.
DotLottie is the newest and uses @lottiefiles/dotlottie-react. This one uses a WASM-based renderer under the hood starting in v1, which means faster playback at scale but a non-trivial first-paint cost if you're loading the WASM cold. In practice, for most SPAs the performance wins show up after the first render, so it matters more for animation-heavy pages than occasional icon animations.
Look, if you're starting a new project today and you don't have an existing Lottie setup, go with lottie-react for control, or DotLottie if you're already on a fast host with good WASM support. Don't reach for @lottiefiles/react-lottie-player unless you actually want the built-in controls UI.
Setting Up lottie-react: The Basics
Install is one line. Then you're importing Lottie and passing it a animationData prop. That's genuinely most of the setup.
npm install lottie-reactimport Lottie from 'lottie-react';
import successAnimation from './animations/success.json';
export function SuccessState() {
return (
<Lottie
animationData={successAnimation}
loop={false}
style={{ width: 120, height: 120 }}
/>
);
}That handles 80% of cases. But what if you need to play an animation imperatively — say, trigger it when a form submits? You use the lottieRef prop to grab the underlying instance:
import { useRef } from 'react';
import Lottie, { LottieRefCurrentProps } from 'lottie-react';
import checkmarkAnimation from './animations/checkmark.json';
export function SubmitButton({ onSubmit }: { onSubmit: () => Promise<void> }) {
const lottieRef = useRef<LottieRefCurrentProps>(null);
const handleClick = async () => {
await onSubmit();
lottieRef.current?.goToAndPlay(0, true);
};
return (
<button onClick={handleClick} className="relative px-6 py-3">
<Lottie
lottieRef={lottieRef}
animationData={checkmarkAnimation}
loop={false}
autoplay={false}
style={{ width: 24, height: 24 }}
/>
Submit
</button>
);
}One more thing — don't bundle your JSON animation files as static imports if they're over 100 KB. Fetch them lazily instead. Large hero animations particularly hit bundle size hard.
DotLottie Setup in React
DotLottie uses a different component and a different file format. The API surface looks similar but it's not a drop-in replacement — there's no animationData prop, you pass a src URL or a path instead. This actually encourages better loading patterns since you're likely hosting the .lottie file on your CDN and fetching it, rather than bundling it.
npm install @lottiefiles/dotlottie-reactimport { DotLottieReact } from '@lottiefiles/dotlottie-react';
export function HeroAnimation() {
return (
<DotLottieReact
src="/animations/hero.lottie"
loop
autoplay
style={{ width: 400, height: 400 }}
/>
);
}For programmatic control with DotLottie, you use the dotLottieRefCallback prop instead of a ref:
import { useCallback, useRef } from 'react';
import { DotLottieReact, DotLottie } from '@lottiefiles/dotlottie-react';
export function PauseableAnimation() {
const dotLottieRef = useRef<DotLottie | null>(null);
const refCallback = useCallback((dotLottie: DotLottie) => {
dotLottieRef.current = dotLottie;
}, []);
return (
<>
<DotLottieReact
src="/animations/loop.lottie"
loop
autoplay
dotLottieRefCallback={refCallback}
/>
<button onClick={() => dotLottieRef.current?.pause()}>Pause</button>
<button onClick={() => dotLottieRef.current?.play()}>Resume</button>
</>
);
}Quick aside: the WASM file that DotLottie pulls in is around 200 KB. In Next.js you'll want to add the WASM to your next.config.js webpack config or use the provided CDN URL so it doesn't break your build. The docs have a specific note on this for Next.js 14+, but the pattern hasn't changed much going into 2026.
Performance Gotchas You'll Actually Hit
The biggest trap is unmounted animations still consuming CPU. lottie-web (which powers lottie-react) keeps an animation instance alive until you explicitly destroy it. If you're mounting and unmounting Lottie components frequently — like in a virtualized list or a modal — you're leaking renderer instances. The lottie-react component handles cleanup on unmount automatically through its internal useEffect, but if you're using lottie-web directly for any reason, call anim.destroy() yourself.
Avoid rendering Lottie animations at sizes larger than you actually display them. The renderer works in logical pixels, so a 400px animation in a 64px container is doing 6x the work for no visual benefit. Set width and height on the container to match your actual display size.
Honestly, the most overlooked issue is animation file quality. Designers sometimes export Lottie JSON with unnecessary layers, hidden elements, or expressions that made sense in After Effects but add zero value in the browser. A tool like the LottieFiles optimizer can strip these out. I've seen a 180 KB animation drop to 42 KB after a pass through the optimizer with no visible change.
If you're triggering animations based on scroll or viewport entry, don't autoplay all of them on mount. Use IntersectionObserver to start the animation when the element enters the viewport — especially important for css scroll animations patterns where multiple animations fire at once. Combine this with autoplay={false} and a ref-driven play() call for the smoothest experience.
One more thing — if you're pairing Lottie animations with a heavily styled component library, make sure the animation container has overflow: hidden if you're animating elements that go right to the edge. Lottie sometimes renders 1–2px outside the logical boundary on certain browser/OS combos at non-integer DPRs.
Integrating Lottie with Your Design System
Wrapping Lottie in a thin component is always worth it, even if you're only using it in two places right now. It lets you enforce sizing conventions, handle the loading state before the animation JSON is ready, and add fallbacks without touching every usage site.
import { Suspense, lazy } from 'react';
import Lottie from 'lottie-react';
type LottieIconProps = {
src: Record<string, unknown>;
size?: number;
loop?: boolean;
className?: string;
};
export function LottieIcon({
src,
size = 32,
loop = true,
className,
}: LottieIconProps) {
return (
<div
className={className}
style={{ width: size, height: size, flexShrink: 0 }}
role="img"
aria-hidden="true"
>
<Lottie
animationData={src}
loop={loop}
style={{ width: size, height: size }}
/>
</div>
);
}Note the role="img" and aria-hidden="true" — if the animation is purely decorative (which most icon animations are), hide it from the accessibility tree. If it conveys meaning, add an aria-label to the wrapper div instead. This is something a lot of tutorials skip.
Lottie pairs well with design systems built around specific visual styles. If you're building a component library with a glassmorphism generator workflow for your backgrounds, animated icons in the Lottie style with luminous strokes and semi-transparent fills can match the frosted aesthetic without custom SVG work. Alternatively, for a neobrutalism style, chunky bold Lottie animations with thick outlines and hard stops fit right in.
Worth noting: if you're building a Next.js app and importing Lottie JSON as static assets, make sure they're in /public and you're fetching them, not importing them — unless you specifically want them bundled. Fetching from /public means they can be cached separately at the CDN layer, which matters when you have 8+ animations across a page.
FAQ
lottie-react is a thin wrapper giving you direct access to the lottie-web instance for programmatic control. @lottiefiles/react-lottie-player is LottieFiles' own component with built-in playback controls UI — more opinionated and slightly heavier.
Use DotLottie (.lottie) if you're on a modern setup and care about file size — it's typically 40–60% smaller. Stick with .json if you need maximum compatibility or are integrating with older tooling that hasn't added DotLottie support yet.
Store them in /public and fetch them at runtime rather than importing them. For the component itself, use next/dynamic with ssr: false since lottie-web is browser-only and will throw on the server.
They can if you autoplay large animations on load — especially hero animations over 150 KB. Use IntersectionObserver to defer play until the element is in view, and always optimize your exported JSON through the LottieFiles optimizer before shipping.