prefers-reduced-motion
The setting is already on the machine, the query has been supported for two years, and the implementation is shorter than the argument about whether to do it.
Somebody has already told their operating system that motion makes them ill. The browser will pass that along if you ask.
@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;
}
}
That blanket version is crude and I use it anyway, because the alternative in practice is nothing at all. Near-zero rather than none because some code waits on animationend and never hears it if the animation does not run.
For a while I got this wrong, thinking it was about tasteful fades. It is not. The animations that cause actual harm are the large ones: parallax, full-screen scroll-jacking, a big element flying across the viewport, anything with real distance in it. A 150ms fade on a button is nobody’s problem. A hero that moves at a different rate to the page is somebody’s whole afternoon.
So the honest version is: run the blanket query, and separately, look at anything on the page that moves more than a few pixels and ask whether it earns its distance.
Reduce is not remove. The user asked for less, not for a static document, and a cross-fade in place of a slide is usually the right answer if you have time to do it properly.