lottie-react

Migration from v2

Every v2 surface, and where it went in v3.

v3 is a rewrite. This page maps the whole v2 surface onto it, one table per area, so an upgrade is a series of small mechanical steps.

Why upgrade

  • Only what you import ships. Lottie costs about 4.4 kB gzipped and useLottie about 3.4 kB, with per-import budgets enforced by the build. v2 shipped one bundled file, so every consumer carried all of it. A whole-package figure for v3 sums three engines and describes no real import: Lottie with its engine is about 81 kB gzipped, the same as all of v2, and LottieLight about 51 kB.
  • Re-renders stopped rebuilding. v2 tore the animation down and reloaded it whenever animationData changed identity or loop flipped. v3 loads once: loop, speed and direction reach the running engine, and src is compared by content.
  • The smaller engines are entry points. LottieSvg and useLottieSvg render with lottie_svg, LottieLight and useLottieLight with lottie_light, and importing either never pulls the full engine.
  • src fetches. A path or URL loads with loading and error overlays and a reload(). v2 took parsed data only.
  • Seeking is an API. Frames, markers, percentages or seconds, plus a scrub gesture, and subscriptions for every playback change.
  • A player ships. LottieControls is an accessible control bar with keyboard shortcuts and fullscreen, paid for only when imported.
  • Interactions are a module. Scroll scrubbing and in-view play, with reduced motion respected.
  • Server rendering is safe by construction, and the library's style defaults always lose to yours.
  • React 18 and 19, both tested against the real versions.

The import

The default export is gone; everything is named:

import Lottie from "lottie-react"; // v2
import { Lottie } from "lottie-react"; // v3

The v2 types went with it: LottieRefCurrentProps and LottieRef become LottieHandle, LottieOptions becomes UseLottieOptions, and the component's props type is LottieProps. LottiePlayer, the re-export of the engine's player object, is removed with no replacement: the engine's global settings are not part of the v3 surface.

Two defaults flipped

v2 played and looped unless told otherwise; v3 does neither unless asked. An animation that should behave as it did in v2 says so:

<Lottie src={anim} autoplay loop />

The component's props

v2v3
animationDatasrc, which also accepts a path or URL to fetch
loop (default true)loop (default false), reactive
autoplay (default true)autoplay (default false)
initialSegmentsegment
style, classNameunchanged, and every other HTML attribute now passes through too
lottieReflottieRef, now typed Ref<LottieHandle>
interactivitythe interactions module
the ten on* event propsthe subscriptions prop, below

Events

subscriptions is one object of handlers in place of the ten props:

v2 propv3 subscription
onCompletecomplete
onLoopCompleteloopCompleted
onEnterFrameframe, with { currentFrame }
onDataReady, onDOMLoaded, onConfigReadyready
onDataFailederror, with { error }
onSegmentStart, onLoadedImages, onDestroyno equivalent

v3 also announces what v2 could not: play, pause, stop, newState, and marker.

The hook

v2's useLottie returned a rendered View plus the methods; v3's returns the instance, and the element is yours:

const { View } = useLottie({ animationData }); // v2
return View;

const lottie = useLottie({ src, autoplay: true, loop: true }); // v3
return <div ref={lottie.setDisplayRef} style={{ height: 300 }} />;

The methods

v2 memberv3
play(), pause(), stop()unchanged
setSpeed(2)unchanged, and it also takes an updater function
setDirection(1) / setDirection(-1)setDirection("forward") / setDirection("reverse")
goToAndStop(value, isFrame)seek(frame), or seek({ seconds }) for time
goToAndPlay(value, isFrame)seek(...) then play()
playSegments(segments, true)playSegments(segments): immediate is the default now
playSegments(segments, false)playSegments(segments, { queue: true })
getDuration()the playableDuration value
getDuration(true)the playableFrames value, a count
setSubframe(...)removed
destroy()removed: unmount the component instead
animationContainerRefref on the component, which is the rendered element
animationLoadedthe state value
animationItemunchanged, still outside the semver promise

Interactivity

useLottieInteractivity and the interactivity prop are replaced by one wrapper and factories:

v2v3
mode: "scroll" with a seek actionlottieScrollScrub({ range })
visibility: [0.4, 0.9]range: [0.4, 0.9]
mode: "cursor"no shipped factory: write one against the same contract
chained actionsno equivalent

Packaging

  • The package exposes only its entry points, so a deep import like lottie-react/build/index.js stops resolving.
  • react and react-dom peers move from 16.8 to 18.2 or newer.
  • On React 18 each animation renders its own copy of the library's small stylesheet; React 19 hoists one copy of each into <head>. Styling behaves the same either way.
  • The build ships one file per module and declares itself side-effect free, so an import pays only for what it reaches.

On this page