Motion Should Explain, Not Decorate
A practical framework for using motion to clarify interface state, hierarchy and cause-and-effect without turning a product into a demo reel.
A lot of animated interfaces look impressive for ten seconds and tiring for the next ten minutes.
The problem is rarely animation itself. The problem is that motion has been added after the interface was already designed, so it has no job beyond making things move.
I prefer a stricter rule:
Motion earns its place when it explains something the static interface cannot explain as clearly.
That explanation might be spatial, temporal, causal or emotional. A panel slides because it came from somewhere. A number accelerates because it is responding to momentum. A section changes tone because the user entered a new context. A button compresses because it received pressure.
The important part is that the motion has a reason.
Start with the state change
Before writing a timeline, describe the interaction without animation.
A useful format is:
State A → user action → State BFor example:
Collapsed filter → click → Expanded filter
Inactive project → scroll into focus → Active project
Card → click → Case-study heroNow ask what the user could misunderstand during that transition.
If a filter simply appears, the user may not know whether it replaced the previous view or expanded from it. If a project card opens a detail page with no visual continuity, the interface feels like two separate systems. If a horizontal archive updates a counter too early, the number communicates a state that the visual composition has not reached yet.
Those are motion problems because they are communication problems.
Animate relationships, not properties
It is easy to think in CSS properties:
gsap.to(card, {
y: -20,
opacity: 1,
duration: 0.8,
});But a better question is: what relationship is changing?
Maybe the card is becoming the dominant object in a group. That relationship can be expressed through several coordinated signals:
- the focused card moves closer,
- surrounding cards lose contrast,
- the background accent changes,
- the index follows the card nearest the viewport center,
- secondary copy appears only after the visual focus is established.
The individual tweens matter less than the sequence they create.
This is why timelines are often more useful than isolated animations. They let the interface express a sentence instead of a collection of unrelated words.
Use one dominant motion idea per scene
Creative interfaces become noisy when every component tries to be memorable.
A scene is stronger when it has one dominant behavior and several quieter supporting behaviors.
For a project archive, the dominant idea might be horizontal travel. The supporting motion can then be subtle:
- artwork parallax follows the horizontal direction,
- project metadata reveals vertically,
- the background hue interpolates between projects,
- the cursor inherits the active project accent.
All four details reinforce the same idea: you are moving through a sequence of distinct project worlds.
If the card also rotates wildly, the title scrambles, the cursor explodes and the background uses an unrelated particle simulation, the interaction stops feeling designed. It starts feeling assembled.
ScrollTrigger is a state machine, not a decoration engine
I use GSAP ScrollTrigger most effectively when I treat it as a state machine.
Instead of thinking:
“When the user scrolls here, animate this thing.”
I think:
“At this region of the document, what state should the interface be in?”
That leads to cleaner logic.
ScrollTrigger.create({
trigger: section,
start: "top center",
end: "bottom center",
onEnter: () => setScene("work"),
onEnterBack: () => setScene("work"),
});The visual system can then respond to scene = "work" consistently rather than letting five different animations independently decide what the page should look like.
This also makes debugging easier. When something looks wrong, I can inspect the current state rather than hunting through a dozen timelines.
Scrub only what should feel physically attached to scroll
Scrubbed animation creates a strong relationship between hand movement and visual movement. That makes it powerful, but also expensive in attention.
I usually scrub things that should feel mechanically connected to the page:
- horizontal tracks,
- progress indicators,
- parallax layers,
- masks opening as an object crosses the viewport,
- large typography drifting with document velocity.
I avoid scrubbing small UI feedback such as buttons, labels or navigation states. Those interactions should complete decisively rather than stopping halfway because the user stopped scrolling.
A good rule is:
Scrub space. Tween intent.
Spatial movement can follow the scroll position. Intentional actions should usually resolve on their own timeline.
Preserve readability during motion
A motion system is not successful if the user has to wait for text to become readable.
This sounds obvious, but many reveal animations begin with opacity: 0, large translations or aggressive clipping on essential content. On a fast scroll, several sections can remain half-revealed at the same time.
For important copy I prefer smaller transformations and shorter reveal distances.
gsap.fromTo(
heading,
{ yPercent: 14, autoAlpha: 0 },
{
yPercent: 0,
autoAlpha: 1,
duration: 0.72,
ease: "power3.out",
},
);The interface still feels alive, but information does not become a hostage to the choreography.
I also avoid animating line-height, width or other layout properties for long-form content. Transform and opacity are usually easier to reason about and less likely to cause layout instability.
Reduced motion should keep the meaning
prefers-reduced-motion should not mean “turn everything off and hope the interface still makes sense.”
The better goal is to preserve state communication while removing unnecessary movement.
For example, a horizontal pinned project scene might become a normal vertical list. A shared-element transition might become a quick crossfade. A kinetic headline might render immediately in its final composition.
The hierarchy remains. The travel disappears.
const mm = gsap.matchMedia();
mm.add("(prefers-reduced-motion: no-preference)", () => {
// expressive choreography
});
mm.add("(prefers-reduced-motion: reduce)", () => {
gsap.set(targets, { clearProps: "all" });
// or render a stable final state without animation
});The reduced version is not an afterthought. It is another valid presentation of the same information architecture.
Motion needs an exit strategy
A surprising number of animation bugs happen after the animation is no longer visible.
Route changes, breakpoint changes and component unmounts can leave behind:
- ScrollTriggers,
- inline transforms,
will-change,- delayed calls,
- pointer listeners,
- stale references to removed DOM nodes.
In React, cleanup is part of motion design.
That is one reason I like scoped GSAP contexts and useGSAP(). A component should own the lifecycle of its motion just as it owns the lifecycle of its state.
The standard I aim for is simple: after leaving a scene, the page should behave as if that scene never existed.
A small test before adding animation
Before I animate an interaction, I ask four questions:
- What state change am I explaining?
- What is the dominant motion idea?
- Can the user read and act before the animation finishes?
- What happens when motion is reduced, interrupted or unmounted?
If I cannot answer the first question, I probably do not need the animation.
If I cannot answer the fourth, the implementation is not finished.
Great interface motion is not about the number of effects. It is about making cause, hierarchy and transition feel obvious without asking the user to think about them.
The best compliment is not “that animation was cool.”
It is when the user simply knows what happened.