Animation that has to know what is happening
Everything so far plays. A Lottie plays, a video plays, a sprite sheet plays. Real interface motion frequently has to respond: a button that is idle, then hovered, then pressed, then loading, then successful, with a sensible transition between any two of those, including the ones nobody planned for: pressed straight to error, hover interrupted halfway to idle.
Building that with play-a-file thinking produces a combinatorial mess. Five states means up to twenty transitions, and a separate clip for each is unmaintainable.
The state machine
The tool for this is a finite state machine: a set of named states, a set of inputs, and rules for which input moves you from which state to which other state. The animation for each state, and each transition between states, is authored once.
Rive is the product built around this idea. You draw and animate in the editor, then define a state machine in the same file: states, transitions, conditions, and inputs that the host application can set. The runtime loads a .riv file and you set inputs from code:
const input = stateMachine.findInput('isLoading');
input.value = true; // the runtime handles the blendThe important part is what the runtime does between states. It blends: if you are 40 per cent through the hover animation when the pointer leaves, it does not snap to idle, it interpolates from where it is. That interruption handling is the thing that is tedious to write by hand and is the main reason to use a tool for it.
Rive files are small (commonly under 20KB for a complex interactive component, because it is vector data plus a state graph) and the runtimes cover web, iOS, Android, Flutter and several engines.
Be clear-eyed about the trade: this is a proprietary format with a commercial product behind it. The runtimes are open source and permissively licensed, and the editor has a free tier with limits that change. A file authored in Rive is editable in Rive.
Doing it without a new dependency
The same architecture works with tools you already have, and for many components it is the better answer.
Lottie with named segments. Author one file with the states laid out along the timeline (frames 0 to 20 idle loop, 21 to 45 hover-in, 46 to 70 press, 71 to 110 success) and drive it from code with playSegments. You write the state machine yourself, in about forty lines. You do not get automatic blending on interruption, so design the segments to be interruptible: return to a common pose at the boundaries.
CSS with a data attribute. For simple components this is the whole thing. Put the state on the element as data-state="loading", write a rule per state, and let transitions handle the in-between. The browser’s transition engine already blends from the current computed value, which means interruption handling is free: this is the one place the platform gives you for nothing what Rive charges for.
.btn { transition: transform 180ms var(--ease-move); }
.btn[data-state="pressed"] { transform: scale(0.96); }
.btn[data-state="loading"] { transform: scale(1); }XState or a hand-rolled reducer for the logic, with any of the above for the visuals. Keeping the state machine in your application code rather than inside an animation file has a real advantage: it is testable, it is in version control next to the component, and a developer can read it without opening a design tool.
The question to decide it
Does the designer need to own the state machine, or does the developer?
If the motion is complex, the states are visual, and the person iterating on it is a designer who should not be opening a pull request for every timing tweak, a tool like Rive earns its place. If the states are application states (loading, error, empty, authorised) they belong in code, because that is where the truth about them already lives, and duplicating them into an animation file guarantees the two will disagree.
The failure mode nobody warns about is the third option: the state machine exists in both places. The animation file knows about isLoading and so does the component, and six months later one of them has a state the other does not.
Scroll as an input
A common case of interactive motion is scroll position driving an animation. It used to require a JavaScript listener recalculating on every scroll event, which is main-thread work at exactly the moment the main thread is busiest.
CSS scroll-driven animations now express this declaratively:
.progress {
animation: grow linear;
animation-timeline: scroll(root block);
}The browser runs it on the compositor, so it stays smooth while scripts run. Where support is missing it degrades to the animation not running, so design the default state to be correct.
The free path
- CSS transitions plus a state attribute: free, native, and handles interruption correctly by default.
- Lottie with segment control: free format, free runtimes, free authoring in Glaxnimate.
- XState: MIT-licensed state machines, with a free visualiser that draws the graph.
- Rive runtimes are open source; the editor has a free tier, and it is worth knowing because job adverts have started naming it.
- Blender with drivers for non-runtime interactive prototyping.
The limitation
A state machine makes the transitions explicit, which is its value and also its cost: you now have to decide what happens for every pair, including the fifteen nobody will ever hit. Teams routinely spend a week on transition polish for states that occur in 0.1 per cent of sessions.
And an interactive animation cannot be checked by watching it once. It needs a test that drives it through the awkward sequences (pressed during loading, hover during exit, two inputs changing on the same frame) because those are where the blending produces something nobody designed.
Every lesson here is open. Your progress is recorded once you pass the Video with AI exam.
Check your understanding
A component’s hover, press, loading and error visuals are driven by a state machine authored inside an animation file, while the application separately tracks the same four states in its own code. Six months later the two disagree. What was the structural mistake?
Pick the one you would defend. Your answer stays private.
Stuck on something?Ask Deepa, your AI tutor
