From brief to breakthrough: giving Frontify's product story a pulse with Rive

How OFF+BRAND built a scroll-driven, click-reactive interactive experience in Rive for Frontify.

From brief to breakthrough: giving Frontify's product story a pulse with Rive

Bryan James
Experience Designer, OFF+BRAND
View author profile
Bryan James
Experience Designer, OFF+BRAND
View author profile
Table of contents

Some briefs define the solution. Frontify's defined the ambition and left the rest to us.

What that ambition became: Frontify, a brand management platform, wanted a scroll-driven product story where every element responds, reacts, and transitions with intention: layers of interactivity running in concert, held together by logic most users will never consciously notice. Building it meant pushing Rive well past its reputation as an animation tool, and drawing on nearly every technique we've developed across years of interactive work. This article is a detailed account of how our team at OFF+BRAND did it, and a practical guide to the Rive capabilities that made it possible.

Why Rive and why it fits this kind of work

Rive is widely known for UI animation. But for projects like this one — diagrammatic, interactive, and performance-sensitive — it earns its place for different reasons. Unlike video, Rive visuals are runtime-active: they respond to what users are doing, not just playing back a fixed sequence. That distinction matters enormously when the goal is to make something feel alive rather than produced.

We've used Rive across many projects requiring scroll syncing, cursor tracking, hover effects, and click interactivity. Frontify's brief brought all of those threads together in one place.

Building a scroll-driven timeline: data binding, joysticks, and blend states

Every scroll-driven Rive experience depends on the same foundational chain: a runtime variable that knows where the user is, a mechanism to translate that into motion, and a state machine to hold it all together. Here's how each piece works, and how we put them together for Frontify.

Data binding is the backbone of everything

The foundation of any scroll-driven Rive experience is data binding,  the mechanism that lets runtime variables influence what's happening inside the animation. For this project, we used a number type variable called scroll_y, controlled from JavaScript outside the Rive file. This single value becomes the backbone of the entire experience.

Tip: Sometimes values can be displayed near the top of your window. You can use negative values in your system, allowing you to scroll downward when debugging.

The image above shows the full set of data binding variables used in this piece. The key one is scroll_y. Controlled from JavaScript, it means the Rive module is being driven by events happening outside of it, in the website itself. That outside-in relationship is what makes the whole scroll experience possible.

Joysticks translate numbers into motion

A number on its own doesn't move a timeline. That's where joysticks come in. A joystick in Rive maps a value between -100% and 100% to a corresponding timeline, acting as the bridge between a data bind value and actual motion.

The main scrolling timeline js_scroll is assigned to the joystick's X axis — this is what scroll_y will drive at runtime.

From here, we create two new timelines: scroll_0 with the joystick set to -100% (the start), and scroll_1000 with it set to 100% (the end). These then get linked into a blend state in the state machine.

The blend state in the state machine, with scroll_0 mapped to 0 and scroll_1000 mapped to -1000 — the numerical values the dev environment uses to communicate with Rive.

The result is a scrubbable timeline: fluid, bidirectional, and entirely controlled by the user's scroll position. The values 0 and -1000 aren't precious; what matters is the relationship between the two states, not the specific numbers.

Working with components: remap, nested timelines, and file hygiene

A complex Rive file can become unwieldy fast, especially when dozens of modules each carry their own animation logic. The way we manage that complexity comes down to three things: how we structure components, how we connect them to the parent timeline, and how we keep the file size honest as the project grows.

Components keep complexity from compounding

With many modules running their own internal logic, components were essential not just for reuse, but for legibility. Each module lives in its own component with its own timeline, which means the main scene stays clean and each piece remains independently workable.

A bird's-eye view of the canvas, each named component houses its own interactions and timeline logic, keeping the main scene navigable no matter how complex the project gets.

Remap is how the parent controls the parts

To control internal component timelines from the parent scroll, we use remap: a simple 0–100% mapping that dictates how far along a component's internal timeline plays based on the parent timeline's position. It's one of the tidier tools in Rive's kit, and it lets a single scroll value cascade down into every module simultaneously.

The remap panel showing scroll_in at 35.4% driven entirely by the parent scroll timeline, with the remapped timeline visible in the parent scene below.

One component, many shapes: a smarter approach to arrow paths

The animated arrow paths required something more than remap alone. Once a line had fully drawn in via scroll, we wanted it to transition into a looping idle state — a continuously flowing line that communicates activity without demanding the user's scroll input.

The challenge: each arrow has a different shape, start point, and curvature. Rather than build separate components for each variation, we used the “edit vertices” tool to create a separate timeline for each path shape inside a single "base" arrow component. Each timeline defines a start position, end position, an extra node along the line, and all the angle information needed to match the designs. The parent component selects the appropriate path timeline via remap, keeping the component count low and the file size lean.

The edit vertices tool in action. Each arrow path variation lives as its own timeline inside a single component, rather than as a separate component for each shape.

This is one example, but the key element is how this approach scales. Apply it consistently across a complex file and the file size optimizations compound: fewer components, less redundancy, better performance where it counts.

Trim path is what makes the line feel alive

With the path architecture in place, the next question was how to make the looping animation itself feel like it has momentum rather than just cycling. Rive's trim path property is what makes this possible, and it's one we reach for constantly.

The mechanic: Trim End moves from 0% to 100% to draw the line in, then Trim Start follows from 0% to 100% to undraw it — but crucially, the start of Trim Start overlaps with the end of Trim End, with finessed easing on both. That overlap is what transforms a basic draw-undraw cycle into something that reads as continuous forward momentum.

The loop fires just before you'd expect it to

To trigger the looping animation at the right moment, we used nested inputs — booleans (true/false variables) with "expose to parent artboard" enabled, which allows the parent scroll timeline to set a component's internal state from outside it. (One practical note worth keeping: data binding should be used for all runtime-connected variables; we use inputs exclusively within the editor now.)

The state machine inside the arrow component, with line-drawn? exposed to the parent artboard, the boolean that tells the component to switch from its draw-in state to its idle looping state.
The parent scroll timeline with line-drawn? keyframed at 95.7%, set just before the line visually completes, so the transition into the looping state feels seamless rather than sequential.

We set the trigger just before the line visually completes — not at the exact end point. That slight anticipation means the loop feels like it grows out of the draw-in rather than following it. It's an approach we apply consistently across scroll timelines: conditions set slightly early create overlaps; conditions set exactly on time create pauses.

There's a second layer, and you have to scroll to find it

Most interactive experiences are one system deep. Frontify needed two: a scroll-driven layer and, waiting at the end of it, a click-driven one that lets users remix the composition entirely. Getting both to coexist without interfering with each other was the most intricate part of the build.

Property groups give the button its own logic

When building scroll-driven experiences, not every element should be revealed via scroll. For key interactive elements like buttons, scrubbing in creates a problem: the button can end up caught in a half-state, visually present but not yet fully formed. That's a trust issue. A button should always feel like it's either there and clickable, or it isn't there at all.

So rather than tie the button's visibility to the scroll timeline directly, we used a property group boolean — btn-visible — that flips to true at the very end of the scroll timeline, triggering the button's appearance on its own independent state machine tree.

The property group panel showing btn-visible set to true at the end of the scroll timeline — the moment the button is cleared to appear.

We've already seen how data bind values can be controlled by code. But what about controlling them from within Rive, or even from inside a timeline? This is where the "target to source" direction becomes important. Rather than having the property group follow the data bind value, we flip the relationship: the property group sets it.

The "target to source" arrow direction — circled here — is what tells the property group to set the data bind value rather than read it.

When btn-visible is set to true as the user reaches the end of the scroll, it sets the data bind value to true as well, which in turn drives the button's state machine.

The button's state machine: btn-off-base transitions to btn-on when btn-visible = true, and back to btn-off when it returns to false.

From the outside, this looks like a lot of steps for a small detail. But details like this are where the experience either holds together or doesn't. When several of them compound across a single piece, the sum is a user experience that feels considered, even if no individual moment announces itself.

Click and scroll: one rule keeps them from clashing

With the button ready, it needs to control an inner timeline running alongside the scrubbable, scroll-driven one. An important thing to keep in mind when working this way: the two timelines should never control the same property. Each one needs to own its properties exclusively. That's what allows them to interact without stepping on each other's toes.

The obvious approach to the remix timeline would be four separate timelines, one for each transition: 1→2, 2→3, 3→4, 4→1. That works, but it creates something detached and hard to maintain — in active projects, adjustments are inevitable, and four disconnected timelines means four places to make changes. The better approach was one central remix timeline controlled by a joystick, with five one-keyframe timelines setting the joystick to -100%, -50%, 0%, 50%, and 100% respectively, each representing a distinct state.

The remix joystick with tl_remixer on the X axis, and the five position timelines in the animations panel — one keyframe each, covering the full range of states.

These five timelines wire into the state machine, transitioning between each other when the btn-clicked conditional is met. Each transition is set to 1.5 seconds — that duration is what controls the joystick as it moves through its range, say from 0% to 50%, over that time. We use linear easing on the transitions specifically because all the actual easing lives inside the remix timeline itself, keeping the motion consistent and adjustable from one place.

The state machine showing all five remix states in a cycle, with the 1.5s transition duration and btn-clicked condition visible in the panel on the right.

All of these elements placed together result in a two-layered interactive system: scroll-driven forward and backward from any point in the cycle, with click-driven state changes layered cleanly on top.

Three neat tricks worth keeping in your toolkit

Not everything in a Rive project is a major architectural decision, but the smaller details often have an outsized impact on how polished the final experience feels. Here are three techniques from this project worth keeping in your toolkit.

1. Use data binding to handle cursor styling at runtime

Rive doesn't have built-in control of cursor styling at runtime on the web, which matters most for clickable elements, where the cursor changing from an arrow to a hand gives users instant visual feedback that something can be clicked. It can still be done though, and it's something we applied throughout this piece.

The pointer cursor visible over a clickable element in the live experience, achieved by bridging Rive's data binding with a CSS handler in JavaScript.

Using data binding, we set a boolean value cursor-pointer from false to true when a specific hover is detected. A JavaScript handler picks that up and applies the relevant CSS cursor style, resulting in cursor: pointer (or any other) being shown to the user.

2. Use one shared component for all hover effects

The interactive diagram needed multiple areas to respond to hover — a subtle lightening effect that adds a layer of tactility, letting users feel the interface respond to their cursor. Rather than build that effect into each element separately, we created one shared component to handle it.

The hover component's simple state machine — hover_off to hover_on — which can be placed into any parent scene, scaled to any size, and masked to fit.

That one component, controlled by a simple state machine tree, can be placed into any parent scene, stretched to fit any element, and masked to respect rounded corners. The benefits compound: consistent behavior across the file, a smaller overall file size compared to the same effect applied in multiple places, and changes that only ever need to be made in one place.

3. Contain raster images in their own components from the start

This one is simple, but it saves significant time in the later phases of a project when adjustments and amendments may be necessary. Our practice is to put all raster images into their own components from the get-go. When working with complex layer trees, image changes can become surprisingly tricky to handle. Containing them in dedicated components keeps everything easy to find and swap out without headache.

Every raster image in the file is contained in its own named component, making it easy to locate and swap at any stage of the project.

The best interactive experiences hide their complexity

Frontify came with an ambitious brief for its product overview page, a layered interactive story that needed to respond to users, reward exploration, and hold together technically at every level. That kind of ambition is what pushes Rive past its obvious use cases.

What this project shows is how far the tool can go when it's treated as a full interaction design environment: data binding connecting the outside world to what's inside the file, timeline separation letting two interactive systems coexist cleanly, components keeping complexity from compounding. The result is an experience that feels alive and effortless, and proof of what becomes possible when the structural work is done right.

That's the standard worth building toward in any Rive project. One that forces you to think in systems rather than sequences, structure for maintainability before complexity demands it and build experiences with a level of craft that matches the ambition behind them.


Last Updated
May 20, 2026
Category

Related articles


verifone logomonday.com logospotify logoted logogreenhouse logoclear logocheckout.com logosoundcloud logoreddit logothe new york times logoideo logoupwork logodiscord logo
verifone logomonday.com logospotify logoted logogreenhouse logoclear logocheckout.com logosoundcloud logoreddit logothe new york times logoideo logoupwork logodiscord logo

Get started for free

Try Webflow for as long as you like with our free Starter plan. Purchase a paid Site plan to publish, host, and unlock additional features.

Get started — it’s free
Watch demo

Try Webflow for as long as you like with our free Starter plan. Purchase a paid Site plan to publish, host, and unlock additional features.