Rive Micro-Interactions with Next.js

Build a showcase page with Rive animations controlled by state machines.

Introduction

30 Second Summary

The best apps feel alive. Buttons bounce when you click them, icons respond when you hover, and animations react to what you do on the page.

In this project, you will build a micro-interaction showcase page using Rive animations and Next.js. You will learn how to control animation state machines from JavaScript, creating hover, click, and autoplay interactions that you can reuse in any React app.

What You'll Build

A dark-themed landing page with multiple Rive animations controlled by different JavaScript interaction patterns, built with Next.js and Tailwind CSS.

By the end of this project, you'll have:

  • A reusable RiveAnimation component that autoplays any Rive file with a state machine.
  • A RiveHover component that plays on mouse enter and pauses on mouse leave.
  • A RiveTrigger component that fires a state machine trigger on click.
  • A responsive showcase page composing all three interaction patterns into a polished layout.
  • Secret Mission: A number-input-driven progress bar component that lets users drag a slider to control animation progress in real time.

Are there any prerequisites?

This project assumes you are familiar with React components, the Next.js App Router, and basic Tailwind CSS utility classes. You do not need any prior experience with Rive.

Set Up Your Next.js Project with Rive

Before you can build interactive animations, you need a project to put them in. Next.js gives you a production-ready React framework with built-in routing, server-side rendering, and styling support.

In this step, you'll scaffold a Next.js project with Tailwind CSS for styling, then install the Rive React library that powers all the animations in this project.

In this step, get ready to:
  • Create a new Next.js project with Tailwind CSS and TypeScript.
  • Install the Rive animation library.
  • Start the dev server and confirm it runs.
Create a new Next.js project

You'll use create-next-app to scaffold a fully configured project in one command. The flags you pass tell it exactly what features to include, so it won't prompt you with questions.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or Command Prompt (Windows) and press Enter to open it.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your Next.js project by running this command:
npx create-next-app@latest rive-showcase --tailwind --typescript --eslint --app --src --no-import-alias

What does this command do?

This command scaffolds a complete Next.js project with everything pre-configured:

  • --tailwind includes Tailwind CSS for utility-based styling.
  • --typescript adds TypeScript for type safety.
  • --app uses the App Router (the modern Next.js routing system).
  • --src puts your code inside a src/ directory for cleaner organization.
  • --no-import-alias keeps the default @/ path alias that Next.js provides.

Seeing a prompt about installing create-next-app?

If npx asks "Need to install the following packages: create-next-app@latest. Ok to proceed? (y)", type y and press Enter. This only happens the first time.

  • Once the scaffolding finishes, move into the project folder by running:
cd rive-showcase
  • Confirm you're in the right folder by running:
ls

You should see files like package.json, next.config.ts, tailwind.config.ts, and a src/ folder in the output.

Command not found or permission error?

  • If you see "npx: command not found", your Node.js installation may not be on your system PATH. Reinstall Node.js from nodejs.org and restart your terminal.
  • If you see a permission error on macOS, try prefixing the command with sudo.

Help me troubleshoot my create-next-app command

Install the Rive React library

The @rive-app/react-canvas package provides React hooks for loading and controlling Rive animations. It uses a canvas renderer that works reliably with Next.js server-side rendering.

  • Install the Rive React package by running:
npm install @rive-app/react-canvas
  • Verify it was added to your project by running:
cat package.json | grep rive

You should see @rive-app/react-canvas listed in the dependencies section.

Why @rive-app/react-canvas?

Rive offers two React renderers: react-canvas and react-webgl2. The canvas version has broader browser compatibility and fewer edge cases with Next.js server-side rendering. The APIs are identical between both packages, so everything you learn here applies to either one.

npm install failing?

  • Make sure you're inside the rive-showcase folder. Run pwd to check your current directory.
  • If you see a network error, check your internet connection and try again.

Help me fix my npm install error

Start the dev server

With everything installed, you can start the Next.js development server to confirm the project runs correctly.

  • Start the development server by running:
npm run dev
  • Open your browser and go to http://localhost:3000.

You should see the default Next.js welcome page with the Next.js logo and links to documentation.

Page not loading?

  • Check your terminal for error messages. If port 3000 is already in use, Next.js will suggest a different port in the terminal output.
  • Make sure you ran npm run dev from inside the rive-showcase folder, not your Desktop.

Help me get my Next.js dev server running

Your project is scaffolded and the Rive library is ready to go. Next up, you'll build your first Rive component and see an animation play in the browser.

Create Your First Rive Client Component

Your Next.js project is running and @rive-app/react-canvas is installed. Now it's time to get a Rive animation rendering on screen.

Rive animations run on a browser canvas, which means they need access to browser APIs. In Next.js App Router, components that use browser APIs must be marked as client components with the "use client" directive. You'll create a reusable wrapper component that handles this boundary cleanly.

In this step, get ready to:
  • Create a reusable RiveAnimation client component that autoplays any Rive file.
  • Display the vehicles animation on your homepage.
Create the RiveAnimation component file

The useRive hook from @rive-app/react-canvas takes a Rive file URL and a state machine name, then returns a ready-to-render React component. Wrapping this hook in its own component isolates the client boundary and makes it reusable across your app.

  • In the VS Code Explorer sidebar, right-click the src folder and select New Folder.
  • Name the folder components and press Enter.
  • Right-click the new components folder and select New File.
  • Name the file RiveAnimation.tsx and press Enter.
  • Paste the following code into RiveAnimation.tsx:
"use client";

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

// Define the props this component accepts
interface RiveAnimationProps {
  src: string;
  stateMachine: string;
  className?: string;
}

export default function RiveAnimation({
  src,
  stateMachine,
  className,
}: RiveAnimationProps) {
  // useRive returns a ready-to-render component and a rive instance
  const { RiveComponent } = useRive({
    src,
    stateMachines: stateMachine,
    autoplay: true,
  });

  // Wrap in a div so the parent can control sizing via className
  return (
    <div className={className}>
      <RiveComponent />
    </div>
  );
}

What does this code do?

"use client" tells Next.js this component runs in the browser, not on the server. Rive needs browser canvas APIs, so this directive is required.

The useRive hook accepts an object with src (the .riv file URL), stateMachines (which state machine to activate), and autoplay: true (start playing immediately). It returns a RiveComponent that renders the animation canvas.

The wrapper div with the className prop gives parents control over sizing. Without explicit dimensions, the Rive canvas renders at 0x0 pixels.

Why isolate Rive in its own component?

Rive's documentation recommends isolating useRive into dedicated wrapper components. This prevents animation restart issues when React re-renders a parent. It also keeps the "use client" boundary tight so the rest of your app stays server-rendered.

  • Save RiveAnimation.tsx.

You won't see a change yet because nothing imports this component. The next chunk wires it into your page.

Render the animation on your page

Your component is ready. Now you'll replace the default Next.js homepage with a minimal page that imports RiveAnimation and points it at a Rive file hosted on their CDN.

  • Open src/app/page.tsx in the VS Code Explorer sidebar.
  • Select all the existing code with Cmd+A (macOS) or Ctrl+A (Windows) and delete it.
  • Paste the following code:
import RiveAnimation from "@/components/RiveAnimation";

// Rive CDN hosts example .riv files for testing
const RIVE_SRC = "https://cdn.rive.app/animations/vehicles.riv";
const STATE_MACHINE = "bumpy";

export default function Home() {
  return (
    <main className="min-h-screen bg-gray-950 text-white">
      <section className="flex flex-col items-center justify-center px-6 py-20">
        <RiveAnimation
          src={RIVE_SRC}
          stateMachine={STATE_MACHINE}
          className="w-full max-w-md h-64"
        />
      </section>
    </main>
  );
}

What does this code do?

  • RIVE_SRC points to vehicles.riv, a sample Rive file hosted on their CDN. It contains animated vehicles with a state machine called "bumpy".
  • The className="w-full max-w-md h-64" gives the animation container a max width and a fixed height of 16rem. Without explicit height, the canvas would collapse to zero.
  • This page is a server component by default. Because RiveAnimation has its own "use client" directive, Next.js handles the boundary automatically.
  • Save page.tsx.
  • Switch to your browser at http://localhost:3000. You should see a dark page with an animated vehicle bouncing along a bumpy road.

Don't see the animation?

  • Check that your file is saved at exactly src/components/RiveAnimation.tsx. A typo in the folder or file name causes an import error.
  • Look at your terminal for errors. If you see "Module not found", confirm the @/components/RiveAnimation import path matches your file location.
  • If the page loads but the animation area is blank, make sure you included the className="w-full max-w-md h-64" prop. Without it, the canvas has zero height.

Help me debug why my Rive animation isn't showing up in Next.js

✔️ Awesome, I've got everything!

Great. Double check you've saved both files before moving on.

ⓧ I'd like to double check the full code

Here is the complete code for both files. Make sure yours matches exactly.

"use client";

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

interface RiveAnimationProps {
  src: string;
  stateMachine: string;
  className?: string;
}

export default function RiveAnimation({
  src,
  stateMachine,
  className,
}: RiveAnimationProps) {
  const { RiveComponent } = useRive({
    src,
    stateMachines: stateMachine,
    autoplay: true,
  });

  return (
    <div className={className}>
      <RiveComponent />
    </div>
  );
}
import RiveAnimation from "@/components/RiveAnimation";

const RIVE_SRC = "https://cdn.rive.app/animations/vehicles.riv";
const STATE_MACHINE = "bumpy";

export default function Home() {
  return (
    <main className="min-h-screen bg-gray-950 text-white">
      <section className="flex flex-col items-center justify-center px-6 py-20">
        <RiveAnimation
          src={RIVE_SRC}
          stateMachine={STATE_MACHINE}
          className="w-full max-w-md h-64"
        />
      </section>
    </main>
  );
}

Your first Rive animation is playing on autopilot. Next up, you'll build components that respond to hover and click interactions.

Add Hover and Click Interactions

Your autoplay component from the last step proves Rive animations render correctly in Next.js. But autoplay is only one interaction pattern.

Real micro-interactions respond to what users do. In this step, you'll build two new components that control a state machine through mouse events. One plays on hover. The other fires a trigger on click.

In this step, get ready to:
  • Create a hover-to-play component that starts and stops the animation on mouse enter and leave.
  • Create a click-to-trigger component that fires a Rive state machine trigger.
  • Render both components on the page and verify the interactions work.
Create the hover-to-play component

The hover component is almost identical to RiveAnimation, with two key differences. It starts paused (autoplay: false) and it needs a reference to the rive instance so it can call play() and pause() manually.

  • Create a new file at src/components/RiveHover.tsx and add the following code:
"use client";

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

interface RiveHoverProps {
  src: string;
  stateMachine: string;
  className?: string;
}

export default function RiveHover({
  src,
  stateMachine,
  className,
}: RiveHoverProps) {
  // Destructure both rive (the instance) and RiveComponent (the canvas element)
  const { rive, RiveComponent } = useRive({
    src,
    stateMachines: stateMachine,
    autoplay: false,
  });

  return (
    <div className={className}>
      <RiveComponent
        onMouseEnter={() => rive && rive.play()}
        onMouseLeave={() => rive && rive.pause()}
      />
    </div>
  );
}

What does this code do?

The useRive hook returns both the rive instance (which gives you programmatic control) and the RiveComponent (which renders the canvas). Your previous component only needed RiveComponent because autoplay handled everything.

Setting autoplay: false means the animation loads but stays frozen until you call rive.play(). The rive && guard is necessary because rive is null until the WASM runtime finishes loading.

  • Save RiveHover.tsx.

You won't see a change in the browser yet. The next substep imports this component into the page.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

"use client";

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

interface RiveHoverProps {
  src: string;
  stateMachine: string;
  className?: string;
}

export default function RiveHover({
  src,
  stateMachine,
  className,
}: RiveHoverProps) {
  const { rive, RiveComponent } = useRive({
    src,
    stateMachines: stateMachine,
    autoplay: false,
  });

  return (
    <div className={className}>
      <RiveComponent
        onMouseEnter={() => rive && rive.play()}
        onMouseLeave={() => rive && rive.pause()}
      />
    </div>
  );
}
Create the click-to-trigger component

Triggers are a special type of state machine input in Rive. Unlike booleans or numbers, a trigger has no value. You just "fire" it, and the state machine transitions to the next state.

  • Create a new file at src/components/RiveTrigger.tsx and add the following code:
"use client";

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

interface RiveTriggerProps {
  src: string;
  stateMachine: string;
  triggerName: string;
  className?: string;
}

export default function RiveTrigger({
  src,
  stateMachine,
  triggerName,
  className,
}: RiveTriggerProps) {
  // Get the rive instance and canvas component
  const { rive, RiveComponent } = useRive({
    src,
    stateMachines: stateMachine,
    autoplay: true,
  });

  // Get a reference to the trigger input from the state machine
  const trigger = useStateMachineInput(rive, stateMachine, triggerName);

  const handleClick = () => {
    if (trigger) {
      trigger.fire();
    }
  };

  return (
    <div className={className} onClick={handleClick}>
      <RiveComponent />
    </div>
  );
}

What does this code do?

The useStateMachineInput hook connects your React code to a specific input defined inside the Rive file's state machine. It takes three arguments: the rive instance, the state machine name, and the input name.

The hook returns null until the Rive runtime finishes loading. That's why handleClick checks if (trigger) before calling fire(). Calling fire() on a trigger input tells the state machine to advance to whatever transition that trigger controls.

  • Save RiveTrigger.tsx.

You won't see a change yet. The next substep imports this component into the page.

Seeing a TypeScript error?

If your editor underlines useStateMachineInput in red, make sure you imported it from @rive-app/react-canvas on the same line as useRive. Both hooks come from the same package.

If you see an error about triggerName not existing on the props interface, double check you added it to the RiveTriggerProps interface above the component.

Still stuck? Help me fix a TypeScript error in my RiveTrigger component.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

"use client";

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

interface RiveTriggerProps {
  src: string;
  stateMachine: string;
  triggerName: string;
  className?: string;
}

export default function RiveTrigger({
  src,
  stateMachine,
  triggerName,
  className,
}: RiveTriggerProps) {
  const { rive, RiveComponent } = useRive({
    src,
    stateMachines: stateMachine,
    autoplay: true,
  });

  const trigger = useStateMachineInput(rive, stateMachine, triggerName);

  const handleClick = () => {
    if (trigger) {
      trigger.fire();
    }
  };

  return (
    <div className={className} onClick={handleClick}>
      <RiveComponent />
    </div>
  );
}
Import and render both components on the page

Both components are ready but invisible because nothing imports them yet. You'll update page.tsx to render them below the hero animation.

  • Open src/app/page.tsx and replace the entire file content with the following code:
import RiveAnimation from "@/components/RiveAnimation";
import RiveHover from "@/components/RiveHover";
import RiveTrigger from "@/components/RiveTrigger";

const RIVE_SRC = "https://cdn.rive.app/animations/vehicles.riv";
const STATE_MACHINE = "bumpy";

export default function Home() {
  return (
    <main className="min-h-screen bg-gray-950 text-white">
      {/* Hero section with autoplay animation */}
      <section className="flex flex-col items-center justify-center px-6 py-20">
        <RiveAnimation
          src={RIVE_SRC}
          stateMachine={STATE_MACHINE}
          className="w-full max-w-md h-64"
        />
      </section>

      {/* Interactive components section */}
      <section className="px-6 py-16 max-w-5xl mx-auto">
        <RiveHover
          src={RIVE_SRC}
          stateMachine={STATE_MACHINE}
          className="w-full h-48 rounded-xl overflow-hidden"
        />
        <RiveTrigger
          src={RIVE_SRC}
          stateMachine={STATE_MACHINE}
          triggerName="bump"
          className="w-full h-48 rounded-xl overflow-hidden cursor-pointer"
        />
      </section>
    </main>
  );
}

What changed in this file?

You added two new imports at the top: RiveHover and RiveTrigger. These are your new client components from the previous substeps.

Below the hero section, a new <section> renders both interactive components. Notice RiveTrigger receives a triggerName="bump" prop. This matches the trigger input defined inside the vehicles.riv state machine.

  • Save page.tsx and check your browser at http://localhost:3000.
  • Hover your mouse over the second animation. It should start playing when your cursor enters and freeze when it leaves.
  • Click the third animation (notice the pointer cursor). You should see the vehicle "bump" in response to your click.

Animation not responding to hover or click?

If the hover animation plays but never pauses, confirm your RiveHover.tsx file has autoplay: false in the useRive options. If it's set to true, the animation starts immediately and never stops.

If the trigger click does nothing, make sure triggerName="bump" in page.tsx matches exactly. The name is case-sensitive and must match what the Rive file defines.

If both animations are completely blank, check your browser console for errors. A 0-height container prevents the canvas from rendering. Make sure each component receives a className prop with an explicit height like h-48.

Still stuck? Help me debug why my Rive hover or click interactions aren't working.

✔️ Awesome, I've got everything!

Great. Double check you've saved all three files before moving on.

ⓧ I'd like to double check the full code

import RiveAnimation from "@/components/RiveAnimation";
import RiveHover from "@/components/RiveHover";
import RiveTrigger from "@/components/RiveTrigger";

const RIVE_SRC = "https://cdn.rive.app/animations/vehicles.riv";
const STATE_MACHINE = "bumpy";

export default function Home() {
  return (
    <main className="min-h-screen bg-gray-950 text-white">
      <section className="flex flex-col items-center justify-center px-6 py-20">
        <RiveAnimation
          src={RIVE_SRC}
          stateMachine={STATE_MACHINE}
          className="w-full max-w-md h-64"
        />
      </section>
      <section className="px-6 py-16 max-w-5xl mx-auto">
        <RiveHover
          src={RIVE_SRC}
          stateMachine={STATE_MACHINE}
          className="w-full h-48 rounded-xl overflow-hidden"
        />
        <RiveTrigger
          src={RIVE_SRC}
          stateMachine={STATE_MACHINE}
          triggerName="bump"
          className="w-full h-48 rounded-xl overflow-hidden cursor-pointer"
        />
      </section>
    </main>
  );
}

You now have three distinct interaction patterns working: autoplay, hover-to-play, and click-to-trigger. Next up, you'll arrange these into a polished showcase landing page with proper layout and card styling.

Build the Showcase Landing Page

Your three Rive wrapper components are built and working. Right now they're just stacked on the page without any context or visual polish.

In this step, you'll compose them into a showcase landing page that looks like a real product demo. You'll add a hero section with a title and subtitle, then arrange the interaction components in a responsive card grid.

In this step, get ready to:
  • Add a hero heading and subtitle above the autoplay animation.
  • Build a responsive card grid showcasing hover and click interactions.
  • Verify that explicit sizing and cursor styles keep the canvas rendering correctly.
Add the hero heading and subtitle

A hero section gives visitors immediate context about what they're looking at. You'll add a large title and a short description above the autoplay animation.

  • In src/app/page.tsx, find the first <section> element. Locate the <RiveAnimation component inside it.
  • Add the following heading and paragraph directly above the <RiveAnimation line:
<h1 className="text-5xl font-bold mb-4 text-center">
  Rive Micro-Interactions
</h1>
<p className="text-gray-400 text-lg mb-10 text-center max-w-2xl">
  Interactive animations powered by Rive state machines, integrated into
  a Next.js app. Hover, click, and explore.
</p>

What does this code do?

  • The h1 uses text-5xl font-bold to create a large, bold page title centered with text-center.
  • The subtitle paragraph uses text-gray-400 for a muted contrast against the dark background. max-w-2xl keeps the text readable by preventing lines from stretching too wide.
  • mb-10 on the paragraph creates breathing room between the subtitle and the animation below.
  • Save page.tsx and check your browser.
  • You should see "Rive Micro-Interactions" as a large white heading, a muted gray subtitle below it, and the vehicles animation playing underneath.

Don't see the heading?

  • Make sure you pasted the h1 and p above the <RiveAnimation line, not below it or outside the <section>.
  • Check that you have matching opening and closing tags for the h1 and p elements.

Help me debug my hero section heading and subtitle.

Build the interaction pattern cards

The second section of the page showcases each interaction pattern in its own styled card. A responsive grid ensures the cards stack on mobile and sit side-by-side on wider screens.

  • In page.tsx, find the second <section> element (the one with max-w-5xl mx-auto).
  • Replace the entire content of that section (everything between the opening and closing <section> tags) with the following code:
        <h2 className="text-3xl font-semibold mb-8 text-center">
          Interaction Patterns
        </h2>
        <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
          {/* Hover card */}
          <div className="bg-gray-900 rounded-2xl p-6 border border-gray-800">
            <h3 className="text-xl font-medium mb-2">Hover to Play</h3>
            <p className="text-gray-400 text-sm mb-4">
              Animation starts on mouse enter and pauses on mouse leave.
            </p>
            <RiveHover
              src={RIVE_SRC}
              stateMachine={STATE_MACHINE}
              className="w-full h-48 rounded-xl overflow-hidden"
            />
          </div>

          {/* Trigger card */}
          <div className="bg-gray-900 rounded-2xl p-6 border border-gray-800">
            <h3 className="text-xl font-medium mb-2">Click to Trigger</h3>
            <p className="text-gray-400 text-sm mb-4">
              Click the animation to fire the bump trigger.
            </p>
            <RiveTrigger
              src={RIVE_SRC}
              stateMachine={STATE_MACHINE}
              triggerName="bump"
              className="w-full h-48 rounded-xl overflow-hidden cursor-pointer"
            />
          </div>
        </div>

What does this code do?

  • grid grid-cols-1 md:grid-cols-2 gap-8 creates a responsive layout. On mobile, cards stack in a single column. On medium screens and above, they sit side-by-side.
  • Each card uses bg-gray-900 rounded-2xl p-6 border border-gray-800 to create a dark container with rounded corners and a subtle border that separates it from the page background.
  • The h3 and description paragraph give each card a label so visitors understand the interaction pattern before trying it.
  • cursor-pointer on the trigger card signals to users that the animation is clickable.
  • Save page.tsx and refresh your browser.
  • You should see an "Interaction Patterns" heading followed by two dark cards side-by-side (or stacked if your window is narrow). The left card plays its animation when you hover. The right card fires the bump animation when you click.

Animations not visible in the cards?

  • If the cards appear but the animation canvas is invisible, check that each Rive component has h-48 in its className. Without an explicit height, the Rive canvas renders at 0px tall.
  • Make sure you didn't accidentally remove the RiveHover or RiveTrigger imports at the top of the file.
  • Check your terminal for errors. A missing closing tag or bracket in JSX will prevent the page from rendering at all.

Help me debug my Rive card grid layout.

Verify canvas sizing and cursor styles

Rive renders to an HTML canvas element. The canvas defaults to 0x0 pixels if its parent container has no explicit dimensions.

  • Confirm that the hero RiveAnimation has className="w-full max-w-md h-64" giving it a taller height than the card animations.
  • Confirm that both card animations use h-48 so the canvas has room to render inside the card.
  • Hover over the "Click to Trigger" card. Your cursor should change to a pointer hand, signaling that the card is interactive.

Why explicit heights matter for Rive

The Rive WASM runtime measures its parent container to size the canvas. If the parent has no height (common with flexbox or grid children), the canvas collapses to nothing. Always set a Tailwind height class like h-48 or h-64 on your Rive wrapper's container div.

✔️ Awesome, I've got everything!

Great. Double check you've saved page.tsx and confirmed all three animations are visible and interactive.

ⓧ I'd like to double check the full code

Here is the complete src/app/page.tsx file at this point:

import RiveAnimation from "@/components/RiveAnimation";
import RiveHover from "@/components/RiveHover";
import RiveTrigger from "@/components/RiveTrigger";

const RIVE_SRC = "https://cdn.rive.app/animations/vehicles.riv";
const STATE_MACHINE = "bumpy";

export default function Home() {
  return (
    <main className="min-h-screen bg-gray-950 text-white">
      <section className="flex flex-col items-center justify-center px-6 py-20">
        <h1 className="text-5xl font-bold mb-4 text-center">
          Rive Micro-Interactions
        </h1>
        <p className="text-gray-400 text-lg mb-10 text-center max-w-2xl">
          Interactive animations powered by Rive state machines, integrated into
          a Next.js app. Hover, click, and explore.
        </p>
        <RiveAnimation
          src={RIVE_SRC}
          stateMachine={STATE_MACHINE}
          className="w-full max-w-md h-64"
        />
      </section>

      <section className="px-6 py-16 max-w-5xl mx-auto">
        <h2 className="text-3xl font-semibold mb-8 text-center">
          Interaction Patterns
        </h2>
        <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
          <div className="bg-gray-900 rounded-2xl p-6 border border-gray-800">
            <h3 className="text-xl font-medium mb-2">Hover to Play</h3>
            <p className="text-gray-400 text-sm mb-4">
              Animation starts on mouse enter and pauses on mouse leave.
            </p>
            <RiveHover
              src={RIVE_SRC}
              stateMachine={STATE_MACHINE}
              className="w-full h-48 rounded-xl overflow-hidden"
            />
          </div>

          <div className="bg-gray-900 rounded-2xl p-6 border border-gray-800">
            <h3 className="text-xl font-medium mb-2">Click to Trigger</h3>
            <p className="text-gray-400 text-sm mb-4">
              Click the animation to fire the bump trigger.
            </p>
            <RiveTrigger
              src={RIVE_SRC}
              stateMachine={STATE_MACHINE}
              triggerName="bump"
              className="w-full h-48 rounded-xl overflow-hidden cursor-pointer"
            />
          </div>
        </div>
      </section>
    </main>
  );
}

Your showcase page is complete. You have a polished, responsive landing page demonstrating three distinct Rive interaction patterns. Next, tackle the Secret Mission to add a fourth pattern that controls animation progress with a range slider.

Secret mission

Add a Number-Input Progress Controller

You've mastered triggers, hover, and autoplay. But Rive state machines can also accept number inputs, letting you control animation progress with pixel-perfect precision. Build a fourth card that connects an HTML range slider directly to a Rive number input.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Next.js project folder rive-showcase on your Desktop.
  • Installed npm packages (@rive-app/react-canvas and node_modules).
  • Local dev server running on http://localhost:3000.
  • Custom component files (RiveAnimation.tsx, RiveHover.tsx, RiveTrigger.tsx).

Keep everything running

No action needed. Choose this if you are still actively building or want to extend the showcase with more Rive animations.

  • Your dev server stays running at http://localhost:3000.
  • All files and dependencies remain in place.

Pause - I'll come back to this later

Shut down the dev server to free up your terminal and system resources. Your project files stay on disk so you can resume anytime.

  • In your terminal, press Ctrl+C to stop the Next.js dev server.
  • When you are ready to resume, navigate back to the rive-showcase folder and run npm run dev.

Delete - I don't want to use this again

Remove all project files from your machine. This is irreversible.

  • In your terminal, press Ctrl+C to stop the dev server if it is still running.
  • Navigate to your Desktop by running cd ~/Desktop.
  • Delete the entire project folder by running rm -rf rive-showcase (macOS/Linux) or Remove-Item -Recurse -Force rive-showcase (Windows PowerShell).

That's a Wrap!

That's a Wrap!

Nice work. You just built a polished micro-interaction showcase powered by Rive and Next.js.

You've learned how to:

  • Built three reusable Rive wrapper components with proper Next.js App Router client component boundaries.
  • Controlled Rive state machines from JavaScript using triggers, play/pause, and the useStateMachineInput hook.
  • Composed a responsive landing page demonstrating real-world interaction patterns used by companies like Spotify, Duolingo, and Google.
  • Added a number-input-driven progress controller for fine-grained animation control.

Ready to quiz yourself?