Moro
Gallery
IFoundations
01Why motion exists in UI
02Timing & duration
03Easing
04Springs vs tweens
IIExpression
05Anticipation
06Follow-through & overlapping action
07Squash, stretch & impact
08Arcs
IIIEngineering
09Performance
10Layout animations & FLIP
11Interruptibility & interaction
12Accessibility & restraint
Tier IFoundations02

Timing & duration

How long, how often, and the rhythm of choreography

The same animation at 200ms and at 800ms is a different animation. One is a tap; the other is a performance. Timing (how long motion lasts and how often it fires) is the single most consequential dimension of an animation, and the one most people guess at. Getting duration right is not a matter of taste. There are rules, and they apply across platforms and decades.

#The idea

Human perception has bands. Under about 100ms, motion feels instantaneous: you don't consciously register it as motion. From 100-300ms, motion feels responsive: you perceive it, but it doesn't delay you. From 300-600ms, motion feels deliberate: the interface is showing you something. Past 600ms, motion starts to feel slow, and past 1000ms it feels broken unless you've explicitly framed it as a long-form transition.

Different UI roles live in different bands:

  • Micro-interactions (button press, checkbox toggle, hover feedback): 100-150ms. If users trigger these 100x a day, even 200ms adds up.
  • Standard UI transitions (dropdown, tooltip, menu open): 150-250ms. Fast enough to not delay; slow enough to register.
  • Content surfaces (modals, drawers, page transitions): 250-400ms. The user is switching mental context; let the motion land.
  • Everything else is suspect. Durations above 500ms usually indicate a decorative animation hiding as a functional one.

Duration is not the whole story. Frequency matters too. The more often a user sees a given animation, the shorter it should be. And when multiple elements animate together, stagger (the tiny delay between their start times) turns choreography into rhythm. A list of ten items appearing simultaneously reads as a cut; the same ten appearing with 30ms stagger reads as a deal of cards.

Canonical example

Watch a Linear issue's detail panel open. The panel slide: ~250ms. The backdrop dim: ~200ms. The issue title's type-in: no delay. All three elements move together as a unit, but at slightly different durations, which is what makes it feel orchestrated rather than mechanical.

Stagger turns a group into a rhythm. Tune the delay to feel the difference between a deal (10-60ms) and a wave (100-200ms).

Controls

0.10s
0.20s
300.00
24.00

Follow-through demo shows stagger in the opposite direction: the bars trail each other as they rise and settle.

Controls

0.60s
0.05s
40.00px

#When it applies

  • Every animation you write. There is no motion without duration.
  • Enter animations should last slightly longer than exits. Entrance is you showing the user something; exit is acknowledging they're done.
  • Staggered lists benefit from stagger when there are 3-8 items. Fewer and it's imperceptible; more and it becomes a long wait.
  • Larger elements animate longer than smaller ones. A 400px modal sliding 400ms is right; a 40px tooltip doing the same feels sluggish.

#When it doesn't

  • Don't stagger if the user expects simultaneity. A form clearing all fields at once should not stagger; that would read as malfunction.
  • Don't match duration to distance blindly. A small chip animating the same duration as a full-screen dialog feels wrong. Scale duration with perceived size, not pixel distance.
  • Don't lengthen duration to make motion feel "premium". Premium-feeling motion is short and well-crafted. Slow motion is perceived as sluggish, not luxurious.

#Implementation

The duration cheat-sheet as constants:

lib/durations.ts
export const DURATION = {
  instant: 80,       // button press, icon swap
  micro: 150,        // hover, tooltip
  standard: 220,     // dropdown, menu, popover
  content: 320,      // modal, drawer, page transition
  showcase: 500,     // first-impression moments only
} as const

Staggering a list with Motion:

StaggeredList.tsx
// Container variants inherit staggerChildren
const list = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: {
      staggerChildren: 0.04,   // 40ms between each child
      delayChildren: 0.1,       // initial pause before first child
    },
  },
}
 
const item = {
  hidden: { opacity: 0, y: 8 },
  visible: { opacity: 1, y: 0 },
}
 
<motion.ul variants={list} initial="hidden" animate="visible">
  {items.map((i) => <motion.li variants={item} key={i} />)}
</motion.ul>
  • staggerChildren values of 30-60ms read as rhythm. 100ms+ feels like a list being read aloud.
  • Cap total stagger time. 20 items × 40ms = 800ms, which is too long. Use Math.min(0.25, i * 0.03) to clamp.

#Craft notes

  • Exit faster than entry. A 300ms slide-in should exit in ~200ms. Exit is acknowledgment, not presentation.
  • Avoid round numbers. 237ms often feels better than 250ms, not because of the 13ms, but because it signals that you tuned the duration rather than picking a default.
  • Stagger the first three, not all ten. Visual momentum is established in the first few items; the rest can cascade at full speed. This keeps lists from feeling interminable.
  • When in doubt, cut 20%. Most animations in most UIs are slightly too long. Shortening by 20% almost always improves the feel.

#Exercises

  1. Time three real UI motions. Open an app and time (with a stopwatch or a screen recorder) three common animations. Compare to the duration table above. Which surprised you?
  2. A/B the same motion. Take a modal or drawer in your own project. Render it three times at 200ms, 350ms, and 500ms. Show to a colleague and ask which feels right without telling them the numbers.
  3. Find bad stagger. Find an app with a list that staggers on load. Is it tuned? Most aren't. Look for stagger delays over 80ms, which almost always feel too slow.

#Study this in the wild

  • Arc's tab switcher. Stagger tuned to under 30ms. Reads as instant but you can feel the rhythm.
  • Linear's list reordering. ~180ms per item. Uses layout animation (lesson 10) to produce natural stagger.
  • Stripe Dashboard sidebar. Sub-menus open at ~200ms. Notice how they exit about 50ms faster.

#Further reading

  • Emil Kowalski: Timing — the most concise modern treatment.
  • Material Design: Duration guidance.
Lesson 01
Why motion exists in UI
Lesson 03
Easing

On this page

The ideaWhen it appliesWhen it doesn'tImplementationCraft notesExercisesStudy this in the wildFurther reading

Built by David Umoru