Rive Interactive Icon — Free Rive Runtime Snippet With SVG Fallback
Rive Interactive Icon · Animations · Plain HTML, CSS & JS · Live preview
Copy & paste
Source Code
<div class="ri-wrap">
<div class="ri-stage">
<canvas id="riCanvas" class="ri-canvas" width="220" height="220" hidden></canvas>
<svg id="riFallback" class="ri-fallback" viewBox="0 0 100 100" width="220" height="220">
<circle class="ri-ring" cx="50" cy="50" r="34" />
<path id="riCheck" class="ri-check" d="M32 52 L45 65 L70 35" />
</svg>
</div>
<p class="ri-status" id="riStatus">Loading Rive animation…</p>
<button class="ri-btn" id="riReplay">Replay</button>
</div>*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#0a0c12;color:#fff;min-height:100vh;display:flex;align-items:center;justify-content:center}
.ri-wrap{display:flex;flex-direction:column;align-items:center;gap:16px;padding:32px}
.ri-stage{width:220px;height:220px;border-radius:24px;background:radial-gradient(circle at 30% 20%,#1c2440,#0d0f1c);display:flex;align-items:center;justify-content:center;border:1px solid #232a44}
.ri-canvas{width:100%;height:100%}
.ri-fallback{display:block}
.ri-ring{fill:none;stroke:#2c355c;stroke-width:4}
.ri-check{fill:none;stroke:#4ade80;stroke-width:6;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:60;stroke-dashoffset:60;animation:ri-draw 1.1s ease .2s forwards infinite}
@keyframes ri-draw{
0%{stroke-dashoffset:60}
40%,60%{stroke-dashoffset:0}
100%{stroke-dashoffset:-60}
}
.ri-status{font-size:13px;color:#7d84a6}
.ri-btn{padding:9px 20px;border-radius:999px;border:1px solid #2e3559;background:#141a2e;color:#fff;font-family:inherit;font-size:13px;font-weight:600;cursor:pointer;transition:border-color .2s}
.ri-btn:hover{border-color:#818cf8}// Rive's real API: point new rive.Rive({ src, canvas, ... }) at a hosted
// .riv file exported from the Rive editor. This sandbox has no .riv asset to
// host, so "src" below points at Rive's own public example file (their demo
// CDN can go offline or change without notice). In production, replace src
// with the URL of your own exported .riv file.
const RIVE_SRC = 'https://cdn.rive.app/animations/vehicles.riv';
const canvas = document.getElementById('riCanvas');
const fallback = document.getElementById('riFallback');
const status = document.getElementById('riStatus');
const replayBtn = document.getElementById('riReplay');
let riveInstance = null;
let usingFallback = false;
function playFallback(reason) {
usingFallback = true;
canvas.hidden = true;
fallback.style.display = 'block';
status.textContent = reason || 'Showing built-in fallback icon.';
const check = document.getElementById('riCheck');
check.style.animation = 'none';
void check.offsetWidth;
check.style.animation = 'ri-draw 1.1s ease .1s forwards';
}
try {
// Wrap the Rive load in try/catch (and its own error callback) so a failed
// fetch, a CORS block, or the CDN being unreachable never leaves the demo
// blank — the SVG fallback below always renders as a safety net.
riveInstance = new rive.Rive({
src: RIVE_SRC,
canvas: canvas,
autoplay: true,
stateMachines: 'State Machine 1',
onLoad: () => {
canvas.hidden = false;
fallback.style.display = 'none';
status.textContent = 'Rive animation loaded from ' + RIVE_SRC;
canvas.width = 220;
canvas.height = 220;
},
onLoadError: () => playFallback('Rive asset failed to load — showing fallback icon.'),
});
} catch (err) {
playFallback('Rive runtime unavailable — showing fallback icon.');
}
replayBtn.addEventListener('click', () => {
if (!usingFallback && riveInstance) {
try {
riveInstance.reset();
riveInstance.play();
return;
} catch (err) {
playFallback('Rive playback failed — showing fallback icon.');
return;
}
}
playFallback();
});Extend with AI
Take This Snippet Further With AI
The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.
What's included
Features
About this UI Snippet
Rive Interactive Icon — The Rive Runtime Pattern, With a Fallback That Never Blanks
Rive is a design tool and runtime for small, state-machine-driven vector animations — icons that react to hover, loading states that blend between phases, character animations with real logic behind them — exported as a compact .riv binary and played back in the browser with new rive.Rive({ src, canvas, stateMachines }). This snippet demonstrates that exact loading pattern, but since a live sandbox has no server to host a custom .riv file on, it points src at Rive's own public example asset and — critically — wraps the whole load in error handling with an always-available animated SVG checkmark as a fallback, so the preview is never blank even if that external asset is unreachable.
The real Rive API surface
new rive.Rive({ src, canvas, autoplay, stateMachines, onLoad, onLoadError }) is exactly how you'd load Rive in production. stateMachines names the state machine (built visually in the Rive editor) that drives the animation's logic — timelines, blend states, inputs — rather than a single fixed timeline. onLoad fires once the .riv file has been fetched and parsed; onLoadError fires if it can't be. Both are used here for real, not simulated.
Why the fallback matters
Embedding a third-party asset URL in a public snippet is fragile — the demo CDN could move, rate-limit, or go offline, and a broken Rive load would otherwise leave a blank canvas with no explanation. This snippet wraps instantiation in try/catch and always renders a small self-contained SVG (a circle with an animated checkmark stroke, using stroke-dasharray/stroke-dashoffset) underneath, toggling visibility so exactly one of "real Rive" or "fallback icon" is showing at any time — never neither.
A pattern worth reusing beyond Rive
Any snippet that depends on a hosted binary asset — a .riv file, a .lottie file, a font, a texture — benefits from the same shape: attempt the real load, catch both synchronous construction errors and the library's own async error callback, and always have a pure-CSS/SVG stand-in ready to show. See Lottie hover icon button for the same guarantee applied to an inline Lottie animation instead of a fallback SVG.
Where Rive fits vs. Lottie
Lottie plays back an After Effects timeline; Rive is closer to a tiny game engine for vector graphics, with state machines and runtime inputs (useful for things like a play/pause icon that blends between states, or a like-button burst with actual physics). Pick Rive when interaction state needs to drive the animation's logic, not just trigger a linear play.
Customizing it
Swap RIVE_SRC for your own exported .riv file's URL, point stateMachines at the state machine name from your file, and use riveInstance.stateMachineInputs(...) to wire booleans/triggers to hover or click events. Redesign the SVG fallback to visually match your real Rive asset so the transition between "still loading" and "loaded" feels seamless.
Step by step
How to Use
- 1Add the Rive runtime CDNInclude @rive-app/canvas from unpkg.
- 2Paste HTML, CSS, and JSA canvas and an SVG fallback both render, one hidden.
- 3Watch it loadThe Rive asset plays on the canvas if it loads.
- 4If the asset failsThe animated SVG checkmark shows instead, never blank.
- 5Click "Replay"Resets and replays whichever is currently active.
- 6Swap in your own .rivChange RIVE_SRC to your exported file's URL.
Real-world uses
Common Use Cases
new rive.Rive runtime..riv file, with a guaranteed animated SVG shown if the file fails to load.try/catch and onLoadError covering both failure paths.Got questions?
Frequently Asked Questions
A live code sandbox has no server to host a custom exported .riv binary on, so this snippet points at one of Rive's own publicly hosted example files purely to demonstrate the real loading API working end to end. In your own project, replace RIVE_SRC with the URL of a .riv file you've exported from the Rive editor and uploaded to your own hosting or CDN.
The rive.Rive constructor call is wrapped in try/catch to guard against synchronous errors, and the onLoadError callback handles asynchronous failures like a network error or the CDN being unreachable. Either path calls the same playFallback() function, which hides the canvas, shows the SVG checkmark icon, and restarts its CSS keyframe animation — so the preview always has something animating, never a blank box.
Rive files can define state machines visually in the Rive editor — named states, transitions, and inputs (booleans, triggers, numbers) that drive which part of the animation plays based on logic rather than a single fixed timeline. Passing stateMachines: "State Machine 1" tells the runtime to drive playback through that state machine; in a real icon you'd typically also call riveInstance.stateMachineInputs() to read or set those inputs from hover/click handlers.
Lottie and GIF both play a fixed, pre-rendered timeline. Rive's state machines let runtime code drive the animation's logic — blending between states, responding to numeric inputs, branching based on booleans — which is closer to a tiny animated state machine than a video loop. That flexibility is the tradeoff for a slightly heavier runtime and the need to design the state machine in the Rive editor first.
Yes. Set autoplay: false in the constructor, then in a mouseenter listener call riveInstance.play() (or set a boolean state machine input to true), and in mouseleave call riveInstance.pause() or reset the input to false. The onLoad callback is the right place to attach those listeners, since riveInstance isn't guaranteed usable until the file has finished loading.