How long should a UI animation actually take?
When an interface feels sluggish, the easing curve usually gets the blame. Most of the time the real problem is the clock: the animation simply runs too long. The useful numbers have been published for years, and they are shorter than most people guess. Here is where they come from and how to pick one for each kind of motion.
Two clocks, not one
Every animation has two timings, and they follow different rules. The first is how quickly the response starts. The second is how long the motion lasts.
The start time is the strict one. Jakob Nielsen’s response-time limits, drawn from Robert Miller’s 1968 research, put the threshold for “instant” at 0.1 seconds. Under that, the result feels caused by the user’s own action. Over it, people start to feel the computer doing something on their behalf. For a button press that 100 ms is a ceiling, not a target. A press should show a visible change on the next frame, which is 16.7 ms at 60 Hz and 8.3 ms on a 120 Hz screen. Flutter’s Material ink ripple fades in over 75 ms, but its first frame is visible right away, and that first frame is what makes the tap feel connected.
Duration is the looser clock. A press can respond in one frame and still run its full animation over 200 ms, and that feels good. What feels bad is a button that does nothing for 150 ms while a fancy transition gets ready.
The ranges the platforms publish
The original Material Design motion guidance is still the clearest written reference. It says mobile transitions typically run around 300 ms. Elements entering the screen take 225 ms, elements leaving take 195 ms, and large full-screen transitions can stretch to 375 ms. It also warns that “transitions that exceed 400ms may feel too slow.” Desktop gets 150–200 ms because pointer users move faster and the distances are smaller relative to what they can see. Tablets get roughly 30% longer than phones (300 ms becomes 390 ms) and wearables roughly 30% shorter (210 ms).
Material 3 swapped those rules of thumb for a token scale, and Flutter ships it as the
Durations class: sixteen steps from short1 at 50 ms to
extralong4 at 1,000 ms, in 50 ms and then 100 ms increments. A scale is not
advice, though. Having a 1,000 ms token does not mean anything in your app should take a
second.
The framework defaults are revealing. Flutter’s theme changes animate over 200 ms
(kThemeAnimationDuration). The iOS-style page push is 500 ms. The newer Android
page transition is 450 ms, and the source comment says that value was “eyeballed on a
physical Pixel 9.” It also notes that native Android takes 800 ms using springs
Flutter can’t yet reproduce. Page transitions run longer than component motion because
the whole screen changes, and because a spring’s long, slow tail doesn’t
feel like waiting. Most of the visible movement is over well before the numbers
settle.
Let size and distance set the number
The most useful rule in that old guidance is the one people skip: duration should scale with how far something travels and how much its surface changes. A checkbox tick moving 12 points needs far less time than a sheet rising across the whole screen. Give both 300 ms and the checkbox will feel lazy while the sheet feels rushed.
These are the working ranges we use. They are a starting point drawn from the guidance above, not a standard:
motion range
press / toggle / checkbox 80–150 ms
tooltip, menu, small fade 120–200 ms
card expand, dialog in 200–300 ms
sheet, full-screen push 300–500 ms
If something sits outside its row, it should be for a reason you could explain. “It looked smoother in the design tool” isn’t one. Prototypes play once, on a big monitor, to someone watching for the motion. Your users see it for the fortieth time that day, on their way to something else.
Exits are faster than entrances
Material’s 225 ms in and 195 ms out is a small gap with a big reason behind it. An
element arriving needs attention, because the user has to see what it is and where it came
from. An element leaving needs none, because the user already decided they were done with
it. Making a closing dialog take as long as an opening one makes people wait to get back to
what they wanted. A rough rule is exits at 70–85% of the entrance. In Flutter that
means setting reverseDuration on the AnimationController or
reverseTransitionDuration on a route, instead of letting the reverse reuse the
forward duration.
Frequency shortens everything
The more often an animation plays, the shorter it should be. Once a motion has done its job of showing where something came from, every repeat is pure delay. A paywall reveal shown once can take its time. A tab switch made 200 times a week should be quick, or have no motion at all. Look at the tab bars in Apple’s own apps: switching tabs is close to instant, while pushing into a detail view slides. That split matches what each one has to explain.
Springs change the question
Apple moved its frameworks toward springs, and since WWDC23 a spring is described by just two
values: a duration and a bounce, as in .spring(duration: 0.6, bounce: 0.2) in
SwiftUI. Apple calls that duration a perceptual duration. It sets the pace and
roughly matches the settling time, but a bouncy spring keeps moving slightly past it.
Apple’s advice is not to wait for the settling time before letting the user continue.
That advice applies to any animation system. Whatever the duration, the animation must
never block input. If someone taps again halfway through, the motion should turn from where
it is toward the new target, not finish the old journey first. Springs do this naturally
because they carry velocity. With a Flutter AnimationController, calling
animateTo or reverse mid-flight starts from the current value, so
the work is mostly not disabling taps while it plays. An interruptible 350 ms animation
feels faster than an uninterruptible 250 ms one.
How to check the numbers
Watch your motion at a slower speed. The iOS Simulator has Slow Animations in its Debug
menu, Android’s developer options have an Animator duration scale, and Flutter exposes
timeDilation in package:flutter/scheduler.dart. At 5× slower you
can see whether things move in the order you meant, whether exits clear before entrances
land, and whether two elements fight over the same moment.
Then judge the timing at full speed, using the app the way users do. Make the same action ten times quickly. If you notice yourself waiting, the duration is too long. If you can’t tell where something came from, it’s too short or travelling too far. We did a quick count of the duration literals in our own Flutter app, and the most common were 200, 150 and 180 ms. That’s roughly where this process ends up for component motion.
prefers-reduced-motion (web), don’t just
shorten the slide. Swap travel for a short crossfade of around 150 ms, so state changes are
still visible but nothing moves across the screen. Flutter reports the setting through
MediaQuery.disableAnimationsOf(context).
The short version: start the response within a frame, keep most motion between 150 and 300 ms, scale it up only for distance, cut it for exits and repeats, and never make anyone wait for it to finish. App Store preview videos have their own pacing rules, which we cover in our preview video guide.