Choosing Animation Durations: How Long Motion Should Last
Easing curves get most of the attention in motion design, but duration does as much to shape how an interface feels. The same curve at 120 milliseconds reads as crisp; at 600 milliseconds, as sluggish; at 60, as a flicker. Most codebases end up with durations chosen one at a time — .3s here, 250ms there, 0.4s on the modal because it "felt right" — and the result is an interface with no consistent rhythm. This page gives a principled way to choose durations from what the animation does, turns the choices into tokens, and shows how to scale durations with component size and remove them for reduced motion. It belongs to Easing & Motion Design in the CSS-Only Micro-Interactions & Animations guide.
Why duration is a design decision
Human perception sets the outer bounds. Changes faster than roughly 100 milliseconds are perceived as instantaneous — useful when you want feedback without the sense of motion. Motion slower than roughly half a second begins to feel like waiting, and users of productivity software notice it quickly because they trigger the same transitions hundreds of times a day. Between those limits is where UI motion lives, and within that range the right value depends on what is moving and how far.
Duration also carries meaning. Fast motion reads as lightweight and responsive; slower motion reads as significant. A toast that slides in over 350 milliseconds says "this is worth noticing"; a dropdown that opens over 350 milliseconds says "you have to wait for this". Matching duration to importance is how motion stays informative instead of decorative.
Duration by kind of change
Three factors push a duration up or down within its band:
- Distance travelled. An element moving 8 pixels needs less time than one moving 400. Duration should grow with distance, but sub-linearly — a doubled distance needs perhaps a third more time, not twice as much, or long movements drag.
- Area changing. A large surface fading or scaling draws more attention than a small one and benefits from a slightly longer duration so the change is not jarring.
- Frequency of use. The more often a transition runs, the shorter it should be. A hover state seen hundreds of times deserves the low end of its band; a celebratory confirmation seen once deserves the high end.
The complete implementation: duration tokens
Durations belong in tokens for the same reason colours do: consistency and a single place to adjust. The system below defines a small scale, maps component types onto it, and handles reduced motion at the token level.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Duration tokens</title>
<style>
:root {
/* The scale: a handful of steps, roughly 1.5x apart. */
--duration-instant: 70ms;
--duration-fast: 140ms;
--duration-base: 220ms;
--duration-moderate: 320ms;
--duration-slow: 450ms;
/* Semantic tokens: what components actually reference. */
--motion-press: var(--duration-instant);
--motion-hover: var(--duration-fast);
--motion-reveal: var(--duration-base);
--motion-panel: var(--duration-moderate);
--motion-page: var(--duration-slow);
}
/* One place turns movement off for users who asked for less. Colour and
opacity changes keep a short duration because they carry state. */
@media (prefers-reduced-motion: reduce) {
:root {
--motion-reveal: 1ms;
--motion-panel: 1ms;
--motion-page: 1ms;
}
}
body { font: 15px/1.5 system-ui, sans-serif; margin: 1.5rem; display: grid; gap: 1rem; }
.button {
justify-self: start;
padding: 0.5rem 1rem;
border: 0;
border-radius: 8px;
background: #4f46e5;
color: #ffffff;
font: inherit;
transition:
background-color var(--motion-hover) ease,
scale var(--motion-press) ease;
}
.button:hover { background-color: #4338ca; }
.button:active { scale: 0.97; }
.button:focus-visible { outline: 2px solid #1e1b4b; outline-offset: 2px; }
.panel {
padding: 1rem;
border-radius: 12px;
background: #eef2ff;
translate: 0 0.5rem;
opacity: 0;
transition:
translate var(--motion-panel) cubic-bezier(0.22, 1, 0.36, 1),
opacity var(--motion-panel) ease;
}
.toggle:checked ~ .panel { translate: 0 0; opacity: 1; }
</style>
</head>
<body>
<button class="button" type="button">Save</button>
<input class="toggle" type="checkbox" id="show" aria-controls="p">
<label for="show">Show details</label>
<div class="panel" id="p">Details slide up over the panel duration.</div>
</body>
</html>
Two layers of tokens are deliberate. The raw scale (--duration-fast and so on) gives designers a small vocabulary to choose from. The semantic tokens (--motion-hover, --motion-panel) are what components use, so changing "how fast do panels open across the product" is one edit, and the reduced-motion override can target movement categories while leaving colour-change durations alone.
The key technique: scaling duration with distance
When the same component appears at very different sizes, a single duration is wrong at one extreme or the other. A drawer that slides 280 pixels on a phone and 640 pixels on a desktop moves more than twice as fast on desktop if the duration is fixed, which feels abrupt. Container query units make it possible to scale duration with the component's size, bounded on both ends.
.drawer-host { container-type: inline-size; }
.drawer {
/* tan(atan2()) turns a length ratio into a plain number. Here the
container width in units of 400px: 0.7 on a 280px phone, 1.6 on a
640px panel. The duration grows with it, clamped to a sane range. */
--room: tan(atan2(100cqi, 400px));
transition: translate clamp(220ms, calc(180ms + 90ms * var(--room)), 380ms)
cubic-bezier(0.22, 1, 0.36, 1);
}
The linear formula inside clamp() gives the "rises, then flattens" behaviour in practice: the constant term dominates for small containers, and the maximum caps large ones. The tan(atan2()) idiom is the standard way to divide one length by another in CSS, since calc() cannot divide lengths directly; its derivation and pitfalls are covered in Motion That Scales With Container Size. For spacing-driven timing, Fluid Spacing Tokens Driving Transition Durations takes the same idea further.
Duration for sequences and loops
Staggered sequences need their own arithmetic. If eight list items each fade in over 220 milliseconds with a 60-millisecond stagger, the sequence takes 220 + 7 × 60 = 640 milliseconds — already past the "feels slow" line. Keep total sequence time under about half a second by shortening the stagger as the count grows, or by staggering only the first few items and revealing the rest together.
Loops are the opposite case: a spinner or pulsing indicator should be slow enough to be calm. A rotation of 0.8 to 1.2 seconds per revolution reads as working; faster spinning reads as anxious. And any loop that runs longer than five seconds needs a way to pause under WCAG 2.2.2, or should stop on its own.
Auditing durations in an existing codebase
Introducing tokens into a mature stylesheet starts with finding out what is already there. A quick inventory is revealing: search the CSS for transition and animation declarations and extract every time value. Most codebases turn up fifteen to thirty distinct durations, many within a few milliseconds of each other — 0.2s, 200ms, .25s, 250ms, 0.3s — which is exactly the inconsistency users perceive as an interface lacking rhythm.
Group the values by what they animate rather than by number. Hover and focus colour changes form one group, menu and tooltip reveals another, dialogs and drawers a third. Each group maps onto one semantic token, and outliers either join the nearest group or earn a documented exception. The exercise usually shrinks the palette to five or six durations without anyone noticing a change — except that the interface suddenly feels more coherent.
Two findings deserve special attention. First, any duration above 500 milliseconds on a routine interaction is a candidate for shortening; those are the transitions users describe as "laggy" without knowing why. Second, any animated movement with no reduced-motion handling needs it, and routing every duration through tokens makes the fix a single media query rather than dozens of overrides. Browser DevTools help here too: the Animations panel in Chromium and Firefox records every animation and transition on the page with its duration and delay, which is a faster inventory for a live page than reading source.
Browser support
Durations in ms and s, custom properties inside transition and animation, and prefers-reduced-motion are supported across all current engines; prefers-reduced-motion dates from Chrome 74, Edge 79, Firefox 63 and Safari 10.1. The container-scaled variant needs container query units (Chrome and Edge 105+, Firefox 110+, Safari 16+) and the trigonometric functions for tan(atan2()), which are supported in current versions of all three engines. Declare a fixed-duration transition first as a fallback.
FAQ
What is a good default duration for UI transitions? Around 150 to 250 milliseconds for small state changes such as hover, focus and toggles, and 250 to 400 milliseconds for larger movements such as panels and dialogs. Anything under about 100 milliseconds is perceived as instant, and anything over about 500 milliseconds starts to feel slow for routine interactions.
Should larger elements animate for longer? Generally yes. A larger element or a longer travel distance needs more time for the eye to follow, so duration should grow with distance, but less than proportionally. Doubling the distance calls for roughly a 20 to 40 percent longer duration, not double.
Should exit animations be shorter than entrances? Usually. Users have already decided to dismiss something, so the exit should get out of the way. Exits commonly run at 60 to 80 percent of the matching entrance duration.
What duration should reduced-motion users get? For movement, none or nearly none. Keep short opacity or colour changes if they help communicate state, since those are not the motion that affects vestibular disorders, but remove travel, scaling, parallax and bounces.
Related
- Easing & Motion Design — the parent guide.
- Entry vs Exit Easing — asymmetric curves and durations.
- CSS Custom Properties Architecture — where motion tokens live.
- Staggered List Animations With Custom Properties — sequence timing in practice.
- Fluid Space Scale With clamp() — the spacing scale durations can follow.
Related articles
More pages in the same section.