Rive playback

Play interactive vector animations designed in the Rive editor.

What it is

A .riv file holds artboards (scenes), timeline animations and state machines: the logic that decides which animation plays when, including reactions to the pointer. Designers build all of that in the Rive editor; the app only loads the file. The runtime is WebAssembly, served here from this site (/rive/rive.wasm) rather than a CDN, which is why only the Rive pages' Content Security Policy allows 'wasm-unsafe-eval'.

When to use it

  • Interactive icons, buttons and illustrations
  • Onboarding and empty-state animations designers can update without code
  • Characters and game-like UI that react to input
Official docs ↗

1. A looping animation

Loading Rive animation…

2. An interactive state machine

Loading Rive animation…

Both files are MIT-licensed examples from Rive's own repositories (see public/rive/CREDITS.md). Files exported from Rive's free plan show a small Rive splash screen.

Show code · src/app/rive-basics/RivePlayer.tsx
"use client";

import "@/lib/rive-runtime";
import { Fit, Layout, useRive } from "@rive-app/react-canvas";
import { useState } from "react";
import { useMotion } from "@/components/motion/MotionProvider";
import { Button, Stage } from "@/components/ui";

type Info = { artboard: string; stateMachines: string[]; animations: string[] };

// Plays a .riv file. Rive files carry their own artboards, timeline
// animations and state machines (the interactive logic); the component lists
// what it found and plays the first state machine, or else the first animation.
export function RivePlayer({ src, label, interactive = false }: { src: string; label: string; interactive?: boolean }) {
  const { reduced } = useMotion();
  const [info, setInfo] = useState<Info | null>(null);
  const [playing, setPlaying] = useState(false);

  const { rive, RiveComponent } = useRive(
    {
      src,
      autoplay: false,
      layout: new Layout({ fit: Fit.Contain }),
      // Called once the file has loaded: inspect it, then start it unless the
      // visitor prefers reduced motion. (Interactive files start either way:
      // they only move when the visitor points at them.)
      onRiveReady: (r) => {
        const found = { artboard: r.activeArtboard, stateMachines: r.stateMachineNames, animations: r.animationNames };
        setInfo(found);
        if (!reduced || interactive) r.play(found.stateMachines[0] ?? found.animations[0]);
      },
      onPlay: () => setPlaying(true),
      onPause: () => setPlaying(false),
      onStop: () => setPlaying(false),
    },
    // Rive pauses rendering by itself while the canvas is off screen.
    { shouldUseIntersectionObserver: true },
  );

  const toggle = () => {
    if (!rive || !info) return;
    if (playing) rive.pause();
    else rive.play(info.stateMachines[0] ?? info.animations[0]);
  };

  return (
    <figure style={{ margin: 0 }}>
      <Stage className="h-65">
        <RiveComponent aria-label={label} role="img" />
      </Stage>
      <div className="mb-2 flex flex-wrap items-center gap-2">
        <Button onClick={toggle} disabled={!info}>{playing ? "Pause" : "Play"}</Button>
        {interactive && <span className="text-sm text-muted">Hover and click the animation: its state machine reacts to the pointer.</span>}
      </div>
      {info && (
        <figcaption className="text-sm text-muted">
          Artboard <code>{info.artboard}</code> · state machines: {info.stateMachines.map((n) => <code key={n}>{n} </code>)}
          {info.stateMachines.length === 0 && "none"} · animations: {info.animations.map((n) => <code key={n}>{n} </code>)}
          {info.animations.length === 0 && "none"}
        </figcaption>
      )}
    </figure>
  );
}
Show code · src/lib/rive-runtime.ts
import { RuntimeLoader } from "@rive-app/react-canvas";

// Load Rive's WebAssembly runtime from this site (public/rive/rive.wasm,
// copied there by scripts/copy-rive-wasm.mjs) instead of the jsDelivr CDN
// Rive uses by default, and turn the CDN fallback off. Import this module
// before any Rive component renders.
RuntimeLoader.setWasmUrl("/rive/rive.wasm");
RuntimeLoader.setWasmFallbackUrl(null);