How to Decouple Animation Logic from Application Code
Keep interaction states, springs and timing out of component code without losing control. A practical architecture for file-based animation and code.
Core Takeaway
Decoupling animation logic from application code means the states, transitions, timing and physics live in one authored asset, while the app only supplies data and receives events. That keeps designers able to edit motion without a code deploy, and keeps component code focused on business logic. It works when the boundary is explicit: inputs in, events out, and a runtime that behaves the same on every target platform.
The problem with animation inside components
When springs, timings and state transitions live inside a React component, every motion change is a code change. Designers cannot tune it, the same interaction is rewritten for SwiftUI and Flutter, and tests have to cover timing indirectly. The opposite extreme is also a problem: an animation file with no clear contract becomes a black box that the app cannot control. The useful architecture is a boundary, not a takeover.
Draw the boundary
The animation asset owns presentation and interaction states. The application owns data, permissions, network calls and business rules. The contract between them is small.
- Inputs: values the app sets, such as pressed, loading, selected or progress
- Events: signals the asset emits, such as confirm, cancel or animation-complete
- Variants: named sizes, themes or platform adjustments
- Fallback: what renders if the asset fails to load
What belongs in the asset
Put anything a motion designer should be able to change without a deploy: spring values, durations, enter and exit paths, state transitions and the visual response to input. Put anything that depends on the app's data model outside it. A button should not know how to call the API. It should know how to look pressed and report that it was pressed.
What belongs in code
Code still decides when an action is allowed, what happens after it succeeds and how errors are handled. It also owns accessibility semantics, focus management and the text that describes the state. Moving motion into a file does not remove those responsibilities. If anything, a clear boundary makes them easier to see.
Testing the contract
Test the asset and the integration separately. The asset should have a small set of states that can be previewed without the app. The integration should test that the right inputs are set and the right events are handled. Add a visual check for the states that are easy to break: loading, error, disabled and reduced motion. This is more useful than asserting a specific duration in a unit test.
Tools that support the boundary
The same boundary can be built with a code-first state machine plus a shared motion token package, or with a visual runtime that plays an authored file. The important part is the contract: inputs in, events out, and a fallback when the asset fails. The vendor matters less than whether the boundary is explicit and tested.
Key Principles to Remember
- Keep states, springs and timing in the authored asset; keep data and rules in the app
- Define a small contract: inputs in, events out, variants and a fallback
- Do not let the animation asset own API calls or business rules
- Test states and integration separately, including reduced motion
- The boundary matters more than whether the asset is a file or a library
Frequently Asked Questions
Sources
- React documentation — React
- Motion overview — Material Design 3