rive-interactive

SKILL.md

Rive Interactive - State Machine-Based Vector Animation

Overview

Rive is a state machine-based animation platform that enables designers to create interactive vector animations with complex logic and runtime interactivity. Unlike timeline-only animation tools (like Lottie), Rive supports state machines, input handling, and two-way data binding between application code and animations.

Key Features:

  • State machine system for complex interactive logic
  • ViewModel API for two-way data binding
  • Input handling (boolean, number, trigger inputs)
  • Custom events for animation-to-code communication
  • Runtime property control (colors, strings, numbers, enums)
  • Cross-platform support (Web, React, React Native, iOS, Android, Flutter)
  • Small file sizes with vector graphics

When to Use This Skill:

  • Creating UI animations with complex state transitions
  • Building interactive animated components (buttons, toggles, loaders)
  • Implementing game-like UI with state-driven animations
  • Binding real-time data to animated visualizations
  • Creating animations that respond to user input
  • Working with designer-created animations requiring runtime control

Alternatives:

  • Lottie (lottie-animations): For simpler timeline-based animations without state machines
  • Framer Motion (motion-framer): For code-first React animations with spring physics
  • GSAP (gsap-scrolltrigger): For timeline-based web animations with precise control

Core Concepts

1. State Machines

State machines define animation behavior with states and transitions:

  • States: Different animation states (e.g., idle, hover, pressed)
  • Inputs: Variables that control transitions (boolean, number, trigger)
  • Transitions: Rules for moving between states
  • Listeners: React hooks to respond to state changes

2. Inputs

Three input types control state machine behavior:

  • Boolean: On/off states (e.g., isHovered, isActive)
  • Number: Numeric values (e.g., progress, volume)
  • Trigger: One-time events (e.g., click, submit)

3. ViewModels

Data binding system for dynamic properties:

  • String Properties: Text content (e.g., username, title)
  • Number Properties: Numeric data (e.g., stock price, score)
  • Color Properties: Dynamic colors (hex values)
  • Enum Properties: Selection from predefined options
  • Trigger Properties: Animation events

4. Events

Custom events emitted from animations:

  • General Events: Custom named events
  • Event Properties: Data attached to events
  • Event Listeners: React hooks to handle events

Common Patterns

Pattern 1: Basic Rive Animation

Use Case: Display a simple Rive animation in React

Implementation:

# Installation
npm install rive-react
import Rive from 'rive-react';

export default function SimpleAnimation() {
  return (
    <Rive
      src="animation.riv"
      artboard="Main"
      animations="idle"
      layout={{ fit: "contain", alignment: "center" }}
      style={{ width: '400px', height: '400px' }}
    />
  );
}

Key Points:

  • src: Path to .riv file
  • artboard: Which artboard to display
  • animations: Which animation timeline to play
  • layout: How animation fits in container

Pattern 2: State Machine Control with Inputs

Use Case: Control animation states based on user interaction

Implementation:

import { useRive, useStateMachineInput } from 'rive-react';

export default function InteractiveButton() {
  const { rive, RiveComponent } = useRive({
    src: 'button.riv',
    stateMachines: 'Button State Machine',
    autoplay: true,
  });

  // Get state machine inputs
  const hoverInput = useStateMachineInput(
    rive,
    'Button State Machine',
    'isHovered',
    false
  );

  const clickInput = useStateMachineInput(
    rive,
    'Button State Machine',
    'isClicked',
    false
  );

  return (
    <div
      onMouseEnter={() => hoverInput && (hoverInput.value = true)}
      onMouseLeave={() => hoverInput && (hoverInput.value = false)}
      onClick={() => clickInput && clickInput.fire()} // Trigger input
      style={{ cursor: 'pointer' }}
    >
      <RiveComponent style={{ width: '200px', height: '100px' }} />
    </div>
  );
}

Input Types:

  • Boolean: input.value = true/false
  • Number: input.value = 50
  • Trigger: input.fire()

Pattern 3: ViewModel Data Binding

Use Case: Bind application data to animation properties

Implementation:

import { useRive, useViewModel, useViewModelInstance,
         useViewModelInstanceString, useViewModelInstanceNumber } from 'rive-react';
import { useEffect, useState } from 'react';

export default function Dashboard() {
  const [stockPrice, setStockPrice] = useState(150.0);

  const { rive, RiveComponent } = useRive({
    src: 'dashboard.riv',
    autoplay: true,
    autoBind: false, // Manual binding for ViewModels
  });

  // Get ViewModel and instance
  const viewModel = useViewModel(rive, { name: 'Dashboard' });
  const viewModelInstance = useViewModelInstance(viewModel, { rive });

  // Bind properties
  const { setValue: setTitle } = useViewModelInstanceString(
    'title',
    viewModelInstance
  );

  const { setValue: setPrice } = useViewModelInstanceNumber(
    'stockPrice',
    viewModelInstance
  );

  useEffect(() => {
    if (setTitle) setTitle('Stock Dashboard');
  }, [setTitle]);

  useEffect(() => {
    if (setPrice) setPrice(stockPrice);
  }, [setPrice, stockPrice]);

  // Simulate real-time updates
  useEffect(() => {
    const interval = setInterval(() => {
      setStockPrice((prev) => prev + (Math.random() - 0.5) * 10);
    }, 1000);

    return () => clearInterval(interval);
  }, []);

  return <RiveComponent style={{ width: '800px', height: '600px' }} />;
}

ViewModel Property Hooks:

  • useViewModelInstanceString - Text properties
  • useViewModelInstanceNumber - Numeric properties
  • useViewModelInstanceColor - Color properties (hex)
  • useViewModelInstanceEnum - Enum selection
  • useViewModelInstanceTrigger - Animation triggers

Pattern 4: Handling Rive Events

Use Case: React to events emitted from Rive animation

Implementation:

import { useRive, EventType, RiveEventType } from 'rive-react';
import { useEffect } from 'react';

export default function InteractiveRating() {
  const { rive, RiveComponent } = useRive({
    src: 'rating.riv',
    stateMachines: 'State Machine 1',
    autoplay: true,
    automaticallyHandleEvents: true,
  });

  useEffect(() => {
    if (!rive) return;

    const onRiveEvent = (event) => {
      const eventData = event.data;

      if (eventData.type === RiveEventType.General) {
        console.log('Event:', eventData.name);

        // Access event properties
        const rating = eventData.properties.rating;
        const message = eventData.properties.message;

        if (rating >= 4) {
          alert(`Thanks for ${rating} stars: ${message}`);
        }
      }
    };

    rive.on(EventType.RiveEvent, onRiveEvent);

    return () => {
      rive.off(EventType.RiveEvent, onRiveEvent);
    };
  }, [rive]);

  return <RiveComponent style={{ width: '400px', height: '300px' }} />;
}

Pattern 5: Preloading Rive Files

Use Case: Optimize load times by preloading animations

Implementation:

import { useRiveFile, useRive } from 'rive-react';

export default function PreloadedAnimation() {
  const { riveFile, status } = useRiveFile({
    src: 'large-animation.riv',
  });

  const { RiveComponent } = useRive({
    riveFile: riveFile,
    artboard: 'Main',
    autoplay: true,
  });

  if (status === 'loading') {
    return <div>Loading animation...</div>;
  }

  if (status === 'failed') {
    return <div>Failed to load animation</div>;
  }

  return <RiveComponent style={{ width: '600px', height: '400px' }} />;
}

Pattern 6: Controlled Animation with Refs

Use Case: Control animation from parent component

Implementation:

import { useRive, useViewModel, useViewModelInstance,
         useViewModelInstanceTrigger } from 'rive-react';
import { useImperativeHandle, forwardRef } from 'react';

const AnimatedComponent = forwardRef((props, ref) => {
  const { rive, RiveComponent } = useRive({
    src: 'logo.riv',
    autoplay: true,
    autoBind: false,
  });

  const viewModel = useViewModel(rive, { useDefault: true });
  const viewModelInstance = useViewModelInstance(viewModel, { rive });

  const { trigger: spinTrigger } = useViewModelInstanceTrigger(
    'triggerSpin',
    viewModelInstance
  );

  // Expose methods to parent
  useImperativeHandle(ref, () => ({
    spin: () => spinTrigger && spinTrigger(),
    pause: () => rive && rive.pause(),
    play: () => rive && rive.play(),
  }));

  return <RiveComponent style={{ width: '200px', height: '200px' }} />;
});

export default function App() {
  const animationRef = useRef();

  return (
    <div>
      <AnimatedComponent ref={animationRef} />
      <button onClick={() => animationRef.current?.spin()}>Spin</button>
      <button onClick={() => animationRef.current?.pause()}>Pause</button>
    </div>
  );
}

Pattern 7: Multi-Property ViewModel Updates

Use Case: Update multiple animation properties from complex data

Implementation:

import { useRive, useViewModel, useViewModelInstance,
         useViewModelInstanceString, useViewModelInstanceNumber,
         useViewModelInstanceColor } from 'rive-react';
import { useEffect } from 'react';

export default function UserProfile({ user }) {
  const { rive, RiveComponent } = useRive({
    src: 'profile.riv',
    autoplay: true,
    autoBind: false,
  });

  const viewModel = useViewModel(rive, { useDefault: true });
  const viewModelInstance = useViewModelInstance(viewModel, { rive });

  // Bind all properties
  const { setValue: setName } = useViewModelInstanceString('name', viewModelInstance);
  const { setValue: setScore } = useViewModelInstanceNumber('score', viewModelInstance);
  const { setValue: setColor } = useViewModelInstanceColor('avatarColor', viewModelInstance);

  useEffect(() => {
    if (user && setName && setScore && setColor) {
      setName(user.name);
      setScore(user.score);
      setColor(parseInt(user.color.substring(1), 16)); // Convert hex to number
    }
  }, [user, setName, setScore, setColor]);

  return <RiveComponent style={{ width: '300px', height: '300px' }} />;
}

Integration Patterns

With Framer Motion (motion-framer)

Animate container while Rive handles interactive content:

import { motion } from 'framer-motion';
import Rive from 'rive-react';

export default function AnimatedCard() {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      whileHover={{ scale: 1.05 }}
    >
      <Rive
        src="card.riv"
        stateMachines="Card State Machine"
        style={{ width: '300px', height: '400px' }}
      />
    </motion.div>
  );
}

With GSAP ScrollTrigger (gsap-scrolltrigger)

Trigger Rive animations on scroll:

import { useRive, useStateMachineInput } from 'rive-react';
import { useEffect, useRef } from 'react';
import gsap from 'gsap';
import ScrollTrigger from 'gsap/ScrollTrigger';

gsap.registerPlugin(ScrollTrigger);

export default function ScrollRive() {
  const containerRef = useRef();
  const { rive, RiveComponent } = useRive({
    src: 'scroll-animation.riv',
    stateMachines: 'State Machine 1',
    autoplay: true,
  });

  const trigger = useStateMachineInput(rive, 'State Machine 1', 'trigger');

  useEffect(() => {
    if (!trigger) return;

    ScrollTrigger.create({
      trigger: containerRef.current,
      start: 'top center',
      onEnter: () => trigger.fire(),
    });
  }, [trigger]);

  return (
    <div ref={containerRef}>
      <RiveComponent style={{ width: '100%', height: '600px' }} />
    </div>
  );
}

Performance Optimization

1. Use Off-Screen Renderer

<Rive
  src="animation.riv"
  useOffscreenRenderer={true} // Better performance
/>

2. Optimize Rive Files

In Rive Editor:

  • Keep artboards under 2MB
  • Use vector graphics (avoid raster images when possible)
  • Minimize number of bones in skeletal animations
  • Reduce complexity of state machines

3. Preload Critical Animations

const { riveFile } = useRiveFile({ src: 'critical.riv' });
// Preload during app initialization

4. Disable Automatic Event Handling

<Rive
  src="animation.riv"
  automaticallyHandleEvents={false} // Manual control
/>

Common Pitfalls and Solutions

Pitfall 1: State Machine Input Not Found

Problem: useStateMachineInput returns null

Solution:

// ❌ Wrong: Incorrect input name
const input = useStateMachineInput(rive, 'State Machine', 'wrongName');

// ✅ Correct: Match exact name from Rive editor
const input = useStateMachineInput(rive, 'State Machine', 'isHovered');

// Always check if input exists before using
if (input) {
  input.value = true;
}

Pitfall 2: ViewModel Property Not Updating

Problem: ViewModel property doesn't update animation

Solution:

// ❌ Wrong: autoBind enabled
const { rive } = useRive({
  src: 'dashboard.riv',
  autoplay: true,
  // autoBind: true (default)
});

// ✅ Correct: Disable autoBind for ViewModels
const { rive } = useRive({
  src: 'dashboard.riv',
  autoplay: true,
  autoBind: false, // Required for manual ViewModel control
});

Pitfall 3: Event Listener Not Firing

Problem: Rive events not triggering callback

Solution:

// ❌ Wrong: Missing automaticallyHandleEvents
const { rive } = useRive({
  src: 'rating.riv',
  stateMachines: 'State Machine 1',
  autoplay: true,
});

// ✅ Correct: Enable event handling
const { rive } = useRive({
  src: 'rating.riv',
  stateMachines: 'State Machine 1',
  autoplay: true,
  automaticallyHandleEvents: true, // Required for events
});

Resources

Official Documentation

Rive Editor

Learning Resources

Related Skills

  • lottie-animations: For simpler timeline-based animations without state machines
  • motion-framer: For code-first React animations with gestures
  • gsap-scrolltrigger: For scroll-driven animations
  • spline-interactive: For 3D interactive animations

Scripts

This skill includes utility scripts:

  • component_generator.py - Generate Rive React component boilerplate
  • viewmodel_builder.py - Build ViewModel property bindings

Run scripts from the skill directory:

./scripts/component_generator.py
./scripts/viewmodel_builder.py

Assets

Starter templates and examples:

  • starter_rive/ - Complete React + Rive template
  • examples/ - Real-world integration patterns
Weekly Installs
35
GitHub Stars
5
First Seen
Feb 27, 2026
Installed on
opencode35
cursor33
gemini-cli33
amp33
cline33
github-copilot33