skip to content

A stylesheet handles reduced motion with a universal rule that sets animation-duration and transition-duration to 0.01ms !important instead of using animation: none. Why the near-zero duration rather than none, and what does that blanket reset still get wrong?

level: seniorimportance: should knowfreq 38%

answer

  1. not zero — nearly zero
  2. something still has to fire
  3. script waits for the end event
  4. infinite iterations never terminate
  5. duration is not the only motion knob

basics

~20 s

A near-zero duration still runs the animation, so animationend and transitionend fire and script that waits on them keeps working; animation: none suppresses them and can leave such code stalled forever. The reset is a safety floor, not a design: it flattens motion instead of substituting something gentler.

solid answer

~50 s

The 0.01ms trick exists because a lot of UI code treats `transitionend` or `animationend` as the signal to run the next step — remove a node, release focus, resolve a promise. `animation: none` or `transition: none` means those events never fire, so that code waits forever; a 0.01ms duration completes almost instantly and still dispatches the event. That is the whole reason for the odd value. What the reset gets wrong is that it is indiscriminate: it applies the same answer to a decorative parallax and to a progress indicator, it usually needs `!important` on `*, *::before, *::after` and so also defeats legitimate per-component overrides, and it does nothing for motion produced outside those two properties — script-driven animation, autoplaying video, animated images. Treat it as a floor under a per-component substitution strategy, not as the strategy.

code

css · 10 lines
css
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

go deeper

for a junior

Recognise the reset block when you see it and know it belongs inside a reduced-motion media query. Be able to say that it shortens durations rather than deleting animations.

for a middle

Explain the mechanism behind the near-zero duration: the animation still completes, so transitionend and animationend still fire. Name what the block does not reach, such as script-driven motion.

for a senior

Argue for it as a floor and describe the per-component substitution strategy on top, including the cascade cost of !important on a universal selector and how you would test the flows that depend on end events.

for a principal

Own the policy question: whether a codebase should carry a global !important hammer at all, what replaces it (a single gated motion token), and how you migrate off it without regressing accessibility during the transition.

## The rule being discussed A version of this block appears in most modern resets: ```css @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } } ``` Every line is doing a specific job, and the interesting one is the choice of `0.01ms` over `none`. ## Why a near-zero duration and not none CSS animations and transitions dispatch events when they finish: `transitionend`, `animationend`, and for repeated animations `animationiteration`. Interfaces lean on these more than people realise. A dialog removes itself from the DOM on `transitionend` after fading out; a toast releases its slot when the exit animation ends; a wizard advances after a slide completes. If you kill the motion with `animation: none` or `transition: none`, the animation or transition never runs, so the terminating event never fires, and that listener waits forever. The visible bug is not "no animation" — it is a dialog that never unmounts or a control that stays disabled. A duration of `0.01ms` sidesteps this. The animation genuinely runs; it just finishes within the same frame, so the user perceives an instant state change while the event still dispatches on schedule. It is a pragmatic hack around a coupling between visual styling and application control flow, and being able to explain that coupling is the point of the question. `animation-iteration-count: 1` is there for the same family of reasons: an `infinite` animation never ends, so shortening its duration alone would leave it flickering forever at high frequency rather than settling. Capping the count makes it terminate. `scroll-behavior: auto` is a separate concern that happens to live in the same block — it undoes `scroll-behavior: smooth`, whose animated scrolling is exactly the kind of large-area motion the preference is about. ## What the reset still gets wrong **It flattens rather than substitutes.** The preference asks for less motion, not for a page where nothing ever changes gracefully. Collapsing every duration to zero throws away the affordance the animation carried — the sense of where a panel came from, which element the new content replaced. A better per-component answer is often to keep a short opacity crossfade and drop only the movement. **It requires `!important` on a universal selector.** That is a large cascade hammer. Once it is in place, a component that legitimately wants a gentle 150ms fade under reduced motion cannot have one without escalating to `!important` itself. Teams that later adopt a token-based approach usually have to unwind this rule first. **It only knows about two properties.** Motion produced any other way is untouched: animation driven from script, an autoplaying `<video>` background, an animated GIF or APNG, a canvas loop, a carousel that advances on a timer, smooth scrolling implemented in code rather than through `scroll-behavior`. The universal selector gives an illusion of totality that does not survive contact with a real page. **It cannot see third-party or shadow content.** Styles inside a shadow root are not reached by a document-level universal selector unless the component opts in, and an inline `style` attribute written by a widget outperforms author rules unless the reset's `!important` happens to apply to that property. **It has no concept of essential motion.** Some motion is the content or the only feedback available: a loading indicator, a progress bar, a video the user pressed play on. Zeroing it can leave the interface silent about work in progress. Small, localised, non-translating indicators are generally acceptable under reduced motion, and a blanket rule cannot make that distinction. ## How to use it well Keep the reset, but treat it as a floor. It guarantees that anything nobody thought about is at least not sliding across the screen, and it covers stylesheets you do not control. Above that floor, handle motion per component: decide for each animation whether reduced motion means removing it, substituting a crossfade, or leaving it alone because it is essential. Where you have a design system, express duration as a custom property gated once in `:root` so components read a token instead of being overridden by a universal `!important` rule — that keeps a single control point without the cascade collateral damage. Finally, verify it. Turn the OS setting on and walk the flows that depend on end events; that is where a `none`-based reset fails loudly and where the 0.01ms version quietly keeps working.

  • Why does the reset cap animation-iteration-count at 1 rather than only shortening the duration?
    Because an `infinite` animation never ends. Shortening its duration alone makes it cycle at very high frequency instead of stopping, which is worse motion, not less, and `animationend` still never fires. Capping the count at 1 makes it run once, finish essentially instantly, and dispatch its end event.
  • Which kinds of motion does this rule fail to cover?
    Anything not expressed through animation-duration or transition-duration: animation driven from script, autoplaying video, animated GIF or APNG, canvas render loops, timer-driven carousels, and hand-rolled smooth scrolling. Styles inside shadow roots and inline style attributes written by third-party widgets also escape a document-level universal selector.
  • Is it acceptable to keep any motion when the preference is set?
    Yes. The preference asks for reduced motion, and essential or small localised motion is generally fine — a loading spinner, a progress bar, a video the user started. What should go is large-area movement: long translations, zooms, parallax, and spinning or bouncing content. Substituting a short opacity crossfade usually keeps the feedback.

saying these in an interview costs you the question

  • Says animation: none and 0.01ms are functionally identical
  • Thinks the universal selector reaches shadow DOM and inline styles
  • Assumes the reset covers script-driven animation and autoplaying video
  • Believes reduced motion means the page must never change visually
  • Cannot explain why infinite iteration counts need capping

context