How to add motion without it feeling gimmicky.
A bounce on one button feels alive. The same bounce on every button, card, and toast in the app feels like a UI kit demo. The line between motion that reads as polish and motion that reads as gimmick isn't taste — it's a short list of physical rules that almost every well-regarded app follows without ever writing them down.
Motion has exactly three jobs
Feedback: confirm that an action landed — a button depressing under a tap, a checkmark settling into place. Continuity: preserve object permanence — a card should expand into its detail view, not cut to it, so the user's mental model of "where did this come from" survives the transition. Attention: direct the eye to what changed — a new item sliding into a list instead of popping in unannounced.
Anything that isn't doing one of those three jobs is decoration. A parallax layer on scroll that doesn't clarify depth, a logo that spins on launch for no operational reason — that's motion attached to nothing, and "attached to nothing" is precisely what reads as gimmicky. Before adding an animation, name which of the three jobs it's doing. If you can't, cut it.
The duration band nobody deviates from
Material Design's guidance, distilled from years of usability testing, puts most UI transitions between 200 and 300 milliseconds: under 100ms reads as jarring — the eye can't track it as motion at all — and past 500ms it reads as sluggish, like the interface is wading through something. Apple's Human Interface Guidelines don't publish exact numbers but land in the same place philosophically, describing the animations that feel most native as "quick and precise" rather than long and expressive.
Treat that band as a default, not a constant. Scale duration to the distance and area
actually moving: a small icon fading in can sit near the bottom of the band, a full-screen
transition near the top — but rarely above it. In Flutter, that's the difference between
Duration(milliseconds: 150) on an AnimatedOpacity and
Duration(milliseconds: 300) on a page-level Hero transition, not a
single constant reused everywhere out of habit.
Curves make it feel physical, not robotic
Constant velocity — a linear curve — looks mechanical because nothing in the physical world
moves that way; real objects accelerate and decelerate. Material's motion spec names four
curves for exactly this: a standard ease-in-out for movement between two onscreen points, a
deceleration curve for things entering the screen (fast in, slow to rest), an acceleration
curve for things leaving (slow start, fast exit), and a sharp curve for small, quick state
changes. Flutter's Curves.easeInOut, Curves.easeOut, and
Curves.easeIn map onto the same three cases directly — pick based on whether the
thing is entering, leaving, or moving between two visible states, rather than defaulting to
whatever the widget ships with.
For anything a finger can interrupt, use a spring
A curve plays out on a fixed timeline from a fixed start to a fixed end. That's fine until a
user's finger comes back mid-drag — then the motion needs to redirect from wherever it
actually is, at whatever velocity it currently has, which a timeline-based curve can't do and
a physics simulation can. That's what a spring is for. SwiftUI's default
.spring() uses a response of 0.55 and a damping fraction of 0.825 — response
controls how quickly the animation chases its target (lower is snappier), damping fraction
controls how much oscillation survives before it settles (1.0 is no bounce at all). Its
.interactiveSpring() variant drops response to 0.15 specifically for motion still
being tracked under a finger, where any lag between touch and object reads as broken. Flutter
has the same physics available through SpringDescription and
SpringSimulation — mass, stiffness, and damping instead of response and damping
fraction, but the same underlying idea: a real spring, not a lookup table of eased values.
The Flutter-specific gotcha that will burn you
The correct way to respect a user's motion preference is to check
MediaQuery.of(context).disableAnimations and fall back to a cross-fade or a hard
cut when it's true. The gotcha is that the flag doesn't mean the same thing on every platform
it runs on. On Android, toggling "Remove animations" in system accessibility settings flips
it reliably. On iOS, Reduce Motion — the setting actually used by people with vestibular
disorders and motion sensitivity — has not reliably surfaced through
disableAnimations, so code that trusts the flag can end up protecting Android
users while doing nothing at all for the iPhone users it was written for. Web support for the
matching prefers-reduced-motion media query is newer still. None of this is
documented prominently enough that you'll notice by reading the API — it only shows up when
you actually flip the OS setting on each platform and watch what your app does, which is
worth doing before you ship anything that leans on it.
When to just not animate
Never make an animation the only channel carrying information — a color that pulses once to signal a change, with no static equivalent, is invisible to anyone who missed the pulse or has it turned off. When Reduce Motion is on, don't skip the state change entirely; swap the transition for an instant cross-fade so the outcome is still communicated, just without the travel. And if you're ever unsure whether a proposed animation is feedback, continuity, or attention — it's decoration, and the honest move is to cut it rather than justify it after the fact.
Where motion actually earns its keep on a listing
Store screenshots are the one surface where none of this applies, because they're static by definition — the whole craft is making a still frame feel alive without a single frame of travel. If a walkthrough of real motion is what would sell your app, that's what an App Store preview video is for, and it's a slot most listings leave completely empty.