Lottie is a JSON-based animation format. You describe vector animations (originally from After Effects via Bodymovin, now also from Claude, Figma, or LottieFiles) as a .json file. It plays back at any size, on web, iOS, Android, and macOS.
- Web:
lottie-webnpm package or CDN. React wrapper:@lottiefiles/react-lottie-player - iOS/macOS:
lottie-spm(Swift Package Manager) — first-class SwiftUI support viaLottieView - Framer: Lottie JSON files drop in natively — no code required
| Context | Use Lottie? | Notes |
|---|---|---|
| Web app UI (React, Astro, vanilla) | ✅ Yes | Loading states, empty states, confirmations, micro-interactions |
| iOS/macOS SwiftUI app | ✅ Yes | lottie-spm is one of the most popular iOS animation libs |
| Phaser game canvas (in-game sprites) | ❌ No | Phaser owns its render pipeline — use Phaser's own animation system |
| Phaser game UI overlay (menus, HUD) | ✅ Yes | Lives in DOM above the canvas |
| Python backend | ❌ No | Backend has no UI layer |
- Caregiving app (CarerCare): Warm, slow, soft. Breathing auras, gentle checkmarks, calm completions. Not flashy.
- Developer tool (Pidgin): Alert, purposeful, branded. Mascot-based identity animations, clear state signals.
- Wrong: Using the same animation style for both. Tone IS part of the design.
- State-change confirmations — timer saved, reply sent, export complete. User took a high-stakes action; animation provides closure.
- Empty states — transforms "nothing here" into "ready when you are." First impression moment.
- Waiting states — question awaiting reply, loading. Animation communicates "something is happening."
- Onboarding/first launch — sets the visual identity before the user has done anything.
Every Lottie in production should answer: what does this communicate that a static UI cannot? If you can't answer that, remove it.
<script src="https://cdnjs.cloudflare.com/ajax/libs/lottie-web/5.12.2/lottie.min.js"></script>function LottiePlayer({ src, loop = false, autoplay = true, onComplete }) {
const ref = useRef(null);
useEffect(() => {
const anim = lottie.loadAnimation({
container: ref.current,
renderer: 'svg',
loop,
autoplay,
path: src,
});
if (onComplete) anim.addEventListener('complete', onComplete);
return () => anim.destroy();
}, [src]);
return <div ref={ref} />;
}npm install @lottiefiles/react-lottie-playerimport { Player } from '@lottiefiles/react-lottie-player';
<Player autoplay loop src="/animations/empty-state.json" style={{ height: 80 }} />Add com.airbnb.ios:Lottie via Swift Package Manager.
import Lottie
LottieView(animation: .named("pigeon-idle"))
.playing(loopMode: .loop)
.frame(width: 120, height: 120)For one-shot play with completion:
LottieView(animation: .named("checkmark"))
.playing()
.animationDidFinish { _ in onComplete() }Use LottieValueProvider to replace hardcoded colors in the JSON with app design tokens:
let colorProvider = ColorValueProvider(UIColor(named: "Moss")!.lottieColorValue)
lottieView.setValueProvider(colorProvider, keypath: AnimationKeypath(keypath: "**.Fill 1.Color"))The workflow that inspired this work (from @uiadrian on Threads):
- Upload SVGs to Claude
- Describe animation type, timing, and element movement in plain English
- Claude generates Lottie JSON ready to integrate
Prompt pattern:
"Generate a Lottie animation JSON for this SVG. It should [describe motion]. Duration: [Xs]. Loop: [yes/no]. Colors: [hex values]. The [element name] should [move/scale/fade] from [A] to [B] with [easing]."
Limitations:
- Complex multi-layer rigs (mascots, characters) still benefit from a human animator or LottieFiles asset
- Simple one-shot micro-interactions (checkmarks, sparks, pulses) Claude handles well
- Always validate the JSON in the LottieFiles preview player before integrating
| # | Animation | Trigger | Tone | Complexity |
|---|---|---|---|---|
| 1 | Session saved checkmark | Stop & Save → success | Calm closure | Easy |
| 2 | Empty state ambient loop | Today screen, no entries | Warm invitation | Easy |
| 3 | Report bars grow on entry | Reports tab opens | Accomplishment | Medium |
| 4 | Timer ambient breathing aura | Timer running | Sustained presence | Easy–Med |
| 5 | Suggestion card spark | AI card mounts | "App understood you" | Easy |
| 6 | Export sent card flies off | Export → Send | Billing confidence | Medium |
| 7 | Category swatch pop | Category selected | Tactile confirmation | Easy |
| 8 | Week total count-up | Reports tab headline | Achievement | Easy–Med |
| 9 | Timer discard soft erase | Discard pressed | Intentional reset | Easy |
| 10 | First-launch idle invitation | Very first open | Living empty state | Medium |
Starting with: #1 (checkmark) + #2 (empty state ambient)
| # | Animation | Trigger | Layer | Complexity |
|---|---|---|---|---|
| 1 | Reply sent — pigeon takes flight | postReply success | SwiftUI | Medium |
| 2 | Empty inbox — pigeon perched idle | Inbox empty state | SwiftUI | Easy–Med |
| 3 | Urgent question pulse | Unread urgent question row | SwiftUI | Easy |
| 4 | Magic link sent — envelope opens | Login submit | SwiftUI | Easy |
| 5 | Pull-to-refresh branded bird | InboxView refresh | SwiftUI | Hard |
| 6 | Question waiting tick/pulse | Unanswered question detail | SwiftUI | Easy |
| 7 | Document delivery drop | DocViewer load | SwiftUI | Easy |
| 8 | Web UI login envelope send | Web login submit | Web | Easy |
| 9 | Deep link badge glow | Push notification tap → message | SwiftUI | Easy–Med |
Starting with: #1 (pigeon flight) + #2 (pigeon idle) — same mascot rig, two states
When an app has a brand identity tied to a character (Pidgin = pigeon), the highest-leverage move is to build one mascot rig with multiple states:
pigeon-idle.json— perched, breathing, eye blink looppigeon-flight.json— launches and exits framepigeon-sleeping.json— for archived/quiet state
One design asset, many moments. Cost: one good commission or careful LottieFiles search.
- LottieFiles — library of free/paid Lottie animations
- LottieFiles Preview Player — validate JSON before integrating
- lottie-web GitHub — web library
- lottie-ios GitHub — iOS/macOS/tvOS library
- LottieFiles React Player — React component