Reduced motion is not "turn the animation off"
13 July 2026 · 5 min read
An element that animates from invisible, told only to stop animating, stays invisible. The page does not lose its motion - it loses its content, for the people who asked for less motion.
prefers-reduced-motion: reduce is usually implemented as a block that sets animation: none on everything and is never looked at again. That is the half of the job that is easy to test, because you can see it working.
The other half is what the element looks like once the animation is gone.
Animating from hidden
An entrance animation starts at opacity: 0. Remove the animation and the element sits at its starting value forever. The markup is present, the tests pass, and the content is invisible - only for the visitors who set the preference, which is to say only for people who will not be in the room when you demo it.
So every class that animates from a hidden state has to be given its visible state back explicitly: opacity: 1 !important alongside the animation: none.
Freezing a decoration in the wrong place
The mirror image is worse, because it adds something rather than removing it. A shine overlay on a button is held off the button by transform: translateX(-100%) and slides across on hover. Null its transform and you have not stopped a moving highlight - you have parked the highlight in the middle of the button and left it there.
An overlay whose only purpose is to animate has no static state worth keeping. Remove it with display: none.
Write the rule down as a test
These are assertions you can make against the stylesheet itself: every class that animates from hidden must be named in a reduced-motion block and must be given opacity: 1; every decorative overlay must be given display: none and must NOT be given transform: none. Both failures are invisible to a developer who does not have the preference set, so a person reviewing the diff will not catch them.