Choosing Safe Animation Durations and Thresholds
Part of Vestibular-Safe Motion Patterns in Accessible Motion Architecture.
The problem: “keep it subtle” is not a specification
Design reviews reject motion that is obviously too much, but “subtle” is not a number an engineer can implement or a checker can verify. Two distinct kinds of limit are hiding under that word. One is a hard safety limit — the WCAG three-flash rule — where crossing the line can trigger a photosensitive seizure. The other is a set of perceptual thresholds for vection and comfort, where crossing the line makes vestibular users unwell without any bright-line spec. This page turns both into concrete ceilings you can put in code.
Root cause: two different failure modes, two different limits
The flash limit and the vection limits fail for unrelated physiological reasons, so they need to be reasoned about separately.
Flashing is a temporal phenomenon. Rapid, large changes in luminance — more than three per second over a sufficiently large area — can provoke seizures in people with photosensitive epilepsy. This is the concern behind WCAG 2.3.1 Three Flashes, and it applies to any pulsing opacity, alternating background-color, or flickering animation regardless of displacement. It is a hard limit: there is a specific rate you must not exceed.
Vection is a spatial phenomenon. Large, coherent displacement across the visual field reads as self-motion, as explained in the vestibular-safe motion overview. Its severity scales with the area that moves, the distance it travels, and its velocity — a small dot sliding a few pixels is nothing; a full-viewport plane sweeping across the screen is a strong trigger. There is no single spec number, so you work from heuristics and gate anything ambiguous behind prefers-reduced-motion.
Because the two are independent, an animation can pass one and fail the other. A slow full-screen slide never flashes but is a serious vection hazard; a tiny icon flickering ten times a second never moves far but breaches the flash limit. You must check both axes.
Decision table: thresholds by motion type
Use this as the concrete specification. Values in the “safe ceiling” column are the limits to stay under; anything above them belongs behind a reduced-motion gate or should be removed.
| Dimension | Safe ceiling | Why | Fallback if exceeded |
|---|---|---|---|
| Flash rate | ≤ 3 per second (aim well under) | WCAG 2.3.1; seizure risk | Slow the cycle; make it single-shot |
| UI transition duration | ~200–500 ms | Longer feels sluggish and prolongs any vection | Shorten; split into discrete steps |
| Translation distance | < ~10% of viewport dimension | Larger areas of flow read as self-motion | Reduce distance; swap for opacity |
| Scale delta | within ~±4% (scale(0.96)–scale(1.04)) |
Large zoom reads as approach | Reduce delta; use opacity emphasis |
| On-screen velocity | Low; avoid fast sweeps | Speed intensifies optical flow | Ease-out to bleed off velocity |
| Rotation | No continuous loops | Rotational vection | Replace with opacity pulse |
| Autoplay | None for non-essential motion | Removes user control | Require a trigger; honour reduced-motion |
| Opacity fade (reduced-motion) | ~150–250 ms, single-shot | No displacement; brief and non-repeating | Acceptable as the safe default |
The last row is the important reassurance: an opacity fade of around 200ms is not just tolerated under reduced motion, it is the recommended replacement for suppressed movement. It contributes no vection and, kept single-shot, never approaches the flash limit. “Reduced motion” should mean this, not a dead interface.
Production code: thresholds encoded as tokens
Encoding the ceilings as custom properties makes them auditable and keeps every animation on the site honest. The full-motion branch stays within the distance and duration caps; the reduced-motion branch drops to a brief, non-directional fade.
:root {
/* Safety and perceptual ceilings, expressed once and reused everywhere */
--motion-duration-ui: 300ms; /* within the 200–500 ms UI cap */
--motion-distance-max: 8px; /* well under 10% of a typical viewport */
--motion-fade: 200ms; /* reduced-motion fallback fade */
}
/* Full-motion: small rise within the distance cap, quick enough to feel crisp */
@media (prefers-reduced-motion: no-preference) {
.toast-enter {
animation: toast-in var(--motion-duration-ui)
cubic-bezier(0.22, 1, 0.36, 1) both;
}
@keyframes toast-in {
from { opacity: 0; transform: translateY(var(--motion-distance-max)); }
to { opacity: 1; transform: translateY(0); }
}
}
/* Reduced-motion: opacity only, brief and single-shot — no displacement, no flashing */
@media (prefers-reduced-motion: reduce) {
.toast-enter {
animation: toast-fade var(--motion-fade) ease both;
}
@keyframes toast-fade {
from { opacity: 0; }
to { opacity: 1; }
}
}
Rendering Impact:
opacity+ a clampedtransform— composite only; the reduced-motion branch removes thetransform, leaving a brief non-directional fade that satisfies both the flash and vection limits.
Because the distance and duration are tokens rather than magic numbers, a review can grep for a raw translateY(60px) or a 2s UI transition and flag it against the ceiling, which is far more reliable than eyeballing “subtlety”. The specific reduced-motion cascade patterns that consume these tokens are covered in prefers-reduced-motion architecture.
Verification checklist
Constraints and trade-offs
- The flash rate is a hard safety limit, not a comfort preference; it applies to every user, so it cannot be gated behind
prefers-reduced-motion— it must always hold. - The distance and velocity ceilings are heuristics, not spec numbers; when a motion sits near a limit, prefer the safer choice because the query cannot tell you how sensitive an individual user is.
- Very short durations can themselves cause an abrupt, jarring change; the 200–500 ms window balances “quick” against “sudden”, and dropping to near-zero is only appropriate when replacing motion with an instant state change.
- Tokenising thresholds only helps if the tokens are actually reused; a hard-coded distance elsewhere in the codebase bypasses the whole safety net, so enforce it in review.
- A brief opacity fade is safe, but a rapidly looping opacity pulse is not — repetition can breach the flash rule even though a single fade is fine.
Related
- Vestibular-Safe Motion Patterns — the parent guide on which motion types trigger symptoms and their safe replacements
- Replacing parallax with accessible alternatives — applies the distance and velocity ceilings to the highest-risk pattern
- WCAG Animation Conformance — the success criteria, including 2.3.1 Three Flashes, that these thresholds map to