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 Docs
:
https://rive.app/docs
React Rive GitHub
:
https://github.com/rive-app/rive-react
Rive Community
:
https://rive.app/community
Rive Editor
Web Editor
:
https://rive.app/community
Desktop App
Available for macOS, Windows
Learning Resources
Tutorials
:
https://rive.app/learn
Examples
:
https://rive.app/community/files
State Machine Guide
:
https://rive.app/docs/state-machine