DEV Community

Fazal Shah
Fazal Shah

Posted on

Adding Lottie Animations to Alpine.js Projects

Lottie animations work naturally with Alpine.js — here's a complete integration guide with x-data, x-init, and interactive controls.


Why Alpine.js + Lottie?

Alpine.js and Lottie are a natural pairing: both are lightweight, both work without a build step, and both excel at progressive enhancement. If you're building a page with Alpine.js and want animations that look like they cost $50k to produce, Lottie delivers them in a 10KB file.

Before touching any code, preview and edit your animation at IconKing — it lets you change colors and convert .json to .lottie without any account.


Setup (No Build Step)

<!-- Alpine.js CDN -->
<script src="https://unpkg.com/alpinejs@3" defer></script>
<!-- lottie-web CDN -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/lottie-web/5.12.2/lottie.min.js"></script>
Enter fullscreen mode Exit fullscreen mode

That's all the setup you need.


Basic Integration with x-init

<div
  x-data="{ anim: null }"
  x-init="
    anim = lottie.loadAnimation({
      container: $el,
      renderer: 'svg',
      loop: true,
      autoplay: true,
      path: '/animations/hero.json'
    })
  "
  style="width: 200px; height: 200px;"
></div>
Enter fullscreen mode Exit fullscreen mode

x-init fires after Alpine initializes the component. $el refers to the root element — no need for a separate document.getElementById.


Interactive Controls

<div x-data="{
  anim: null,
  playing: true,
  init() {
    this.anim = lottie.loadAnimation({
      container: this.$refs.lottieContainer,
      renderer: 'svg',
      loop: true,
      autoplay: true,
      path: '/animations/loader.json'
    });
  },
  toggle() {
    this.playing ? this.anim.pause() : this.anim.play();
    this.playing = !this.playing;
  }
}">
  <div x-ref="lottieContainer" style="width: 150px; height: 150px;"></div>
  <button @click="toggle()" x-text="playing ? 'Pause' : 'Play'"></button>
</div>
Enter fullscreen mode Exit fullscreen mode

Key pattern: use x-ref instead of $el when the container is a child element rather than the root.


Hover-Triggered Animation

<div x-data="{
  anim: null,
  init() {
    this.anim = lottie.loadAnimation({
      container: this.$refs.icon,
      renderer: 'svg',
      loop: false,
      autoplay: false,
      path: '/animations/hover-icon.json'
    });
  }
}"
@mouseenter="anim.play()"
@mouseleave="anim.stop()"
style="display: inline-block; cursor: pointer;">
  <div x-ref="icon" style="width: 40px; height: 40px;"></div>
</div>
Enter fullscreen mode Exit fullscreen mode

Loading State Pattern

A common pattern is showing a Lottie loader while data fetches:

<div x-data="{
  loading: true,
  data: null,
  lottieAnim: null,
  async init() {
    this.lottieAnim = lottie.loadAnimation({
      container: this.$refs.loader,
      renderer: 'svg',
      loop: true,
      autoplay: true,
      path: '/animations/loading.json'
    });
    this.data = await fetch('/api/data').then(r => r.json());
    this.loading = false;
    this.lottieAnim.destroy();
  }
}">
  <div x-show="loading" x-ref="loader" style="width: 80px; height: 80px;"></div>
  <div x-show="!loading">
    <p x-text="data?.message"></p>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Using dotLottie Format

The .lottie format is ~75% smaller. To use it with Alpine.js, swap lottie-web for dotlottie-web:

<script type="module">
  import { DotLottie } from 'https://esm.sh/@lottiefiles/dotlottie-web';

  document.addEventListener('alpine:init', () => {
    Alpine.data('lottiePlayer', () => ({
      dotLottie: null,
      init() {
        this.dotLottie = new DotLottie({
          canvas: this.$refs.canvas,
          src: '/animations/hero.lottie',
          loop: true,
          autoplay: true,
        });
      },
      destroy() {
        this.dotLottie?.destroy();
      }
    }));
  });
</script>

<div x-data="lottiePlayer" x-init="init()" @alpine:destroyed="destroy()">
  <canvas x-ref="canvas" width="200" height="200"></canvas>
</div>
Enter fullscreen mode Exit fullscreen mode

Note: dotlottie-web requires a <canvas> element, not a <div>.


Cleanup on Component Destroy

Alpine doesn't automatically clean up Lottie instances. Use the destroy magic event:

<div x-data="{ anim: null }"
     x-init="anim = lottie.loadAnimation({ container: $el, loop: true, autoplay: true, path: '/anim.json', renderer: 'svg' })"
     @alpine:destroyed="anim?.destroy()">
</div>
Enter fullscreen mode Exit fullscreen mode

Without this, animations keep running in memory even after the element is removed from the DOM.


Workflow

  1. Get .json from LottieFiles or your designer
  2. Preview and edit colors at IconKing
  3. Convert to .lottie at IconKing for smaller files
  4. Add lottie-web via CDN (or dotlottie-web for .lottie format)
  5. Wire up with x-init and x-ref

Alpine.js + Lottie gives you polished animations without a build pipeline. Start with a loading spinner and go from there.

Top comments (0)