reactanimated iconsrivelottietutorial

How to Add Animated Icons to Your React App

A step-by-step guide to integrating Rive and Lottie animated icons into a React application. Includes code examples, performance tips, and common pitfalls.

Adding animated icons to a React app is straightforward once you understand the available libraries and patterns. This guide walks through integrating both Rive and Lottie icons, with practical code examples and tips for keeping your implementation performant.

Prerequisites

This guide assumes you have a React project (React 18+, Next.js 14+, or a Vite-based setup). You will need animated icon files — you can download .riv and .lottie files from Unicorn Icons.

Option 1: Rive Animated Icons in React

Rive's React library provides hooks and components for rendering .riv files with full state machine support.

Installation

bash
copy
npm install @rive-app/react-canvas

Basic Usage

The simplest way to display a Rive animation is with the useRive hook:

tsx
copy
import { useRive } from "@rive-app/react-canvas";

export function CheckmarkIcon() {
  const { RiveComponent } = useRive({
    src: "/icons/checkmark.riv",
    stateMachines: "State Machine 1",
    autoplay: true,
  });

  return <RiveComponent style={{ width: 48, height: 48 }} />;
}

Controlling State Machines

To drive a state machine from your application code, use the useStateMachineInput hook to get references to the state machine inputs:

tsx
copy
import { useRive, useStateMachineInput } from "@rive-app/react-canvas";

export function SubmitButton({ isLoading, isSuccess }) {
  const { RiveComponent, rive } = useRive({
    src: "/icons/submit-button.riv",
    stateMachines: "ButtonState",
    autoplay: true,
  });

  const loadingInput = useStateMachineInput(rive, "ButtonState", "loading");
  const successInput = useStateMachineInput(rive, "ButtonState", "success");

  // Drive the state machine from props
  React.useEffect(() => {
    if (loadingInput) loadingInput.value = isLoading;
  }, [isLoading, loadingInput]);

  React.useEffect(() => {
    if (successInput) successInput.value = isSuccess;
  }, [isSuccess, successInput]);

  return (
    <button type="submit">
      <RiveComponent style={{ width: 24, height: 24 }} />
      Submit
    </button>
  );
}

Performance: Shared Canvas Renderer

When rendering multiple Rive animations on the same page, use the @rive-app/react-canvas-lite package and share a single WebGL context across instances to minimize GPU memory usage:

bash
copy
npm install @rive-app/react-canvas-lite

Then wrap your app in the RiveProvider component to enable context sharing.

Option 2: Lottie Animated Icons in React

For Lottie icons, @lottiefiles/dotlottie-react is the recommended library in 2026 as it supports the newer .lottie format natively.

Installation

bash
copy
npm install @lottiefiles/dotlottie-react

Basic Usage

tsx
copy
import { DotLottieReact } from "@lottiefiles/dotlottie-react";

export function LoadingSpinner() {
  return (
    <DotLottieReact
      src="/icons/spinner.lottie"
      loop
      autoplay
      style={{ width: 48, height: 48 }}
    />
  );
}

Playback Control

To control playback from your application (e.g., pause when a task completes):

tsx
copy
import { DotLottieReact } from "@lottiefiles/dotlottie-react";
import { useState, useCallback } from "react";

export function TaskIcon({ isComplete }) {
  const [dotLottie, setDotLottie] = useState(null);

  const dotLottieRefCallback = useCallback((ref) => {
    setDotLottie(ref);
  }, []);

  React.useEffect(() => {
    if (dotLottie) {
      if (isComplete) {
        dotLottie.pause();
      } else {
        dotLottie.play();
      }
    }
  }, [isComplete, dotLottie]);

  return (
    <DotLottieReact
      src="/icons/task.lottie"
      dotLottieRefCallback={dotLottieRefCallback}
      loop
      autoplay
      style={{ width: 24, height: 24 }}
    />
  );
}

Handling Reduced Motion

Always respect the user's prefers-reduced-motion preference. In React, you can use a custom hook:

tsx
copy
function usePrefersReducedMotion() {
  const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);

  useEffect(() => {
    const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
    setPrefersReducedMotion(mq.matches);
    const handler = (e) => setPrefersReducedMotion(e.matches);
    mq.addEventListener("change", handler);
    return () => mq.removeEventListener("change", handler);
  }, []);

  return prefersReducedMotion;
}

// Usage:
function AnimatedIcon() {
  const reducedMotion = usePrefersReducedMotion();
  if (reducedMotion) return <img src="/icons/static-icon.svg" alt="" />;
  return <RiveComponent style={{ width: 48, height: 48 }} />;
}

Lazy Loading Icons

For icons below the fold, defer loading using React.lazy or IntersectionObserver to avoid loading animation runtimes until they are needed:

tsx
copy
const AnimatedIcon = React.lazy(() => import("./AnimatedIcon"));

// Wrap in Suspense with a static fallback
<Suspense fallback={<img src="/icons/static.svg" alt="" />}>
  <AnimatedIcon />
</Suspense>

Common Pitfalls

  • Loading the runtime on every page — only import Rive or Lottie in components that actually use animations. Tree-shaking and dynamic imports prevent unnecessary bundle bloat.
  • Forgetting to set explicit dimensions — Rive and Lottie canvases without explicit width/height will default to their artboard dimensions, which may not match your layout. Always set style={{ width, height }}.
  • Not unloading on unmount — both libraries handle cleanup automatically when components unmount, but ensure you're not holding references to rive/lottie instances in closures that outlive the component.

Next Steps

Browse the Unicorn Icons library to find animated icons for your project. For platform-specific guides, see React + Rive and React + Lottie.

Unicorn Icons Team

Unicorn Icons Team

The team behind Unicorn Icons — building the best animated icon library for developers and designers.