A button changes on hover. A modal needs to finish leaving before React removes it. A launch page pins the viewport while several elements enter, overlap, and hand control back to normal scrolling.
All three are “animations,” but that label does not tell me which tool to reach for.
When I evaluate an interaction for Hyperiux Vault, I find one question more useful than asking whether the animation is simple, intermediate, or advanced:
Who owns the interaction?
If the browser already owns the relevant state, I usually evaluate CSS first.
If the animation is a consequence of React state, presence, layout, or gestures, I look at Motion.
If the authored sequence itself needs explicit control, especially across several elements or against scroll, I look at GSAP.
And if movement does not improve feedback, continuity, hierarchy, or storytelling, I leave it static.
That model is more useful than treating CSS, Motion, and GSAP as levels you unlock.
Start with ownership, not complexity
The familiar rule is:
Simple animation → CSS
Medium animation → Motion
Advanced animation → GSAP
The hierarchy falls apart as soon as visual complexity and implementation complexity stop matching.
A shared-layout transition between two React states can look elaborate. Cards reorder, an active indicator moves between controls, and one surface appears to grow into another. Motion has layout and layoutId APIs specifically for animation that follows changed layout in React.
Meanwhile, a hero sequence might contain nothing more technically exotic than opacity and transforms. But if six elements need to overlap at deliberate moments, moving one beat earlier should not require recalculating five unrelated delays. A GSAP timeline gives that timing a shared model.
The second animation may look simpler while needing more explicit choreography.
Visual complexity did not route those examples. Their ownership model did.
The interaction ownership model
I use four starting points.
| Who owns the interaction? | What that usually means | First tool to evaluate |
|---|---|---|
| The browser | Hover, focus, pressed state, straightforward visual state changes, predefined keyframes | CSS |
| The React component | Mount/unmount, conditional rendering, layout changes, shared elements, gestures | Motion |
| The choreography | Several elements share timing, sequence control matters, scroll progress drives an authored composition | GSAP |
| Nobody needs to | Movement adds no useful feedback, hierarchy, continuity, or narrative | No animation |
This is a routing model, not a capability table. GSAP can animate hover states. Motion can handle scroll. CSS can produce sophisticated sequences.
The question is which abstraction describes the interaction with the least unnecessary machinery.
Browser-owned state: evaluate CSS first
A button already has hover and focus states. CSS already understands them.
If the design asks for the icon to move four pixels, the background to change, and the button to scale slightly on pointer hover, moving that behaviour into a JavaScript animation system usually adds an abstraction without adding much control.
Transitions fit naturally because the state already exists in the browser:
.button {
transform: translateY(0);
transition: transform 180ms ease;
}
.button:hover {
transform: translateY(-2px);
}
Keyframes extend the same idea when the browser should run a predefined sequence rather than interpolate between two states.
CSS stops being the obvious answer when orchestration starts leaking into the implementation.
If adjusting one duration forces you to recalculate delays elsewhere, time has become a relationship between elements. If React is removing the node you are trying to animate, the problem now includes component lifecycle. If JavaScript keeps measuring layout so that CSS knows where something moved, another abstraction may describe the interaction more clearly.
Watch the orchestration instead of the visual ambition.
Component-owned state: evaluate Motion
Motion becomes particularly useful when movement follows state React already owns: what is mounted, what state is active, how layout changed, or which gesture is occurring.
A modal is the obvious lifecycle example.
React wants to stop rendering it when open becomes false. The interface may need the modal to remain visible long enough to finish its exit transition. Motion's AnimatePresence exists around this exact boundary, allowing exiting elements to animate before they are removed from the DOM.
Layout changes are another strong signal. Adding Motion's layout prop lets an element animate between layouts after a React render changes its position or dimensions. layoutId can connect matching elements between states.
That is a different kind of problem from running a timed sequence. The important event is the render and the resulting layout change.
Motion also provides gesture APIs for hover, tap, focus, and drag, keeping interaction state close to the component producing it.
This is why I do not think of Motion as “GSAP Lite.” Its advantage is not that it sits halfway up an animation difficulty ladder. Its abstractions line up well with component-owned interactions.
Motion also supports scroll-triggered and scroll-linked animation, so the presence of scroll alone does not route the work to GSAP.
A viewport reveal and a pinned, scrubbed product story both involve scrolling. Only one makes scroll position part of a larger choreography.
Choreography-owned state: evaluate GSAP
Sometimes the timeline becomes a first-class part of the design.
Imagine a launch hero:
- a product render enters;
- a mask begins revealing before the first movement ends;
- the heading starts against that overlap;
- supporting copy appears at a named beat;
- the whole sequence needs to be paused or replayed during development.
The individual animations are simple. Their relationships are not.
A gsap.timeline() gives those relationships a shared playhead. Timing can be positioned relative to other moments instead of encoded as independent delays scattered across components.
Scroll choreography strengthens the case.
ScrollTrigger supports behaviours such as triggering, scrubbing, and pinning. If a feature story keeps a claim fixed while product states change against scroll progress, the implementation now coordinates pin duration, scroll distance, sequencing, and the return to normal document flow.
At that point, time, or scroll mapped onto time, is part of the system.
React still owns the component lifecycle
Using an imperative animation API does not make React lifecycle irrelevant.
GSAP provides useGSAP() through @gsap/react to scope animation work and clean up GSAP objects created during the hook's execution when that scope is reverted. Animations created later in event handlers may need contextSafe() so they participate in the same cleanup model.
In a Next.js App Router setup, a component using useGSAP() needs the appropriate client boundary.
This matters more than it does in a demo. Route changes, repeated mounts, listeners, and ScrollTriggers can expose lifecycle mistakes that are invisible during a single polished run-through.
A worked example: an animated product filter
Suppose a React product page has a filter panel.
Opening the filter changes the panel state. Selecting an option reorders the product cards. An active filter indicator moves between controls. On narrow screens, the filter becomes a modal sheet.
There are plenty of animations here. The first question is still ownership.
The button's hover treatment is browser-owned. CSS is sufficient.
The panel appears and disappears because React state changes. The mobile sheet needs an exit before unmounting. The grid reflows after filtering, and the active indicator changes layout with the selected state.
Those requirements are component-owned, so Motion is the first library I would evaluate.
Now imagine the design changes.
Selecting a filter launches a deliberately authored sequence: the current cards compress, the heading shifts, a loading treatment crosses the grid, new cards enter in staggered groups, and the entire sequence needs exact replayable timing.
The interaction has changed categories. React still determines which products exist, but choreography is now carrying a significant part of the design. GSAP becomes a more reasonable candidate.
The visible interface may have changed only slightly. The ownership of the motion changed a lot.
That is the decision I care about.
The tools still overlap
Ownership narrows the choice; it does not settle every project decision.
A basic viewport reveal can reasonably be built with CSS and browser APIs, Motion, or GSAP depending on the surrounding system. A team already using GSAP heavily may prefer to keep a small additional animation inside the same mental model. A Motion-heavy application may have little reason to introduce another dependency for one sequence that Motion already expresses clearly.
Maintenance matters alongside capability.
The first tool to evaluate is not automatically the tool you must use.
| Interaction | First evaluation | Reconsider when… |
|---|---|---|
| Hover/focus/press state | CSS | Runtime coordination appears |
| Mount/unmount transition | Motion | The sequence extends far beyond component lifecycle |
| Layout/shared element | Motion | Authored timing dominates the interaction |
| Multi-element timed sequence | GSAP | The sequence is simple enough to remain declarative |
| Pinned scroll narrative | GSAP | A simpler non-pinned treatment communicates the same idea |
| Decorative motion with no useful job | None | There is a reason for the movement |
Production changes the answer
Tool selection does not solve the mobile version, reduced motion, cleanup, or runtime cost.
I usually run the chosen interaction through three additional gates.
1. Does the input still exist?
A hover-led effect has no direct equivalent on a touchscreen. A desktop cursor interaction may need to disappear or become a different interaction entirely.
Pinned scrolling also deserves a mobile rethink. A wide-screen composition can keep a claim fixed while evidence changes beside it. Compressing the same behaviour into a narrow viewport can leave the user dragging through several screens of artificially constrained content.
The fallback needs to be designed, not merely disabled.
2. What happens when motion should be reduced?
The prefers-reduced-motion media feature gives the interface a signal that the user has requested less non-essential movement.
That does not necessarily mean replacing every transition with zero duration.
A large pan, parallax effect, or scroll-linked camera move may deserve a static alternative. A subtle opacity transition may still communicate a state change without producing the same motion burden.
The static state also needs to remain readable and usable when animation never runs.
3. What work survives after the animation?
Check what continues running when the interaction moves offscreen. Check whether listeners, observers, timelines, and ScrollTriggers are cleaned up. Look for repeated layout measurement and unnecessary continuous work.
A demo can hide all of this because demos are unusually polite environments.
Production pages are not.
This is how Vault approaches dependencies too
The same ownership logic influences how we think about interactions inside Hyperiux Vault.
The catalogue does not force every effect through one animation engine. Dependencies vary by effect: an interaction may use CSS, Motion, GSAP, browser APIs, or a graphics stack when the behaviour actually requires it.
The Vault dependency model exposes those dependencies at the effect level.
CSS, Motion, and GSAP can all be used without Vault. Vault becomes relevant when you have already decided that a page needs a particular interaction and would rather start from an implementation than rebuild its behaviour from an empty component.
Because the effect lands as editable source in the project, the surrounding decisions can still change: timing, layout, breakpoints, interaction behaviour, reduced-motion handling, and whatever else the final product requires.
The rule I keep
When choosing between CSS, Motion, and GSAP in React, I ask who owns the behaviour.
If the browser already owns the state, CSS is difficult to beat for clarity.
If the animation follows the component tree, Motion gives React concepts an animation model that fits them closely.
If the sequence itself needs authored control, GSAP makes that choreography explicit.
And if nobody can explain what the movement is helping the user understand, I leave it out.
The tool follows the interaction.
That order is much easier to maintain than choosing an animation library first and asking the interface to justify it afterward.
Top comments (0)