Reduced Motion Is Not No Motion
prefers-reduced-motion is not a kill switch. Remove decorative animation, but keep functional animation for state changes and navigation — just simplified.
prefers-reduced-motion: reduce is the most commonly misimplemented accessibility feature in animation-heavy UIs. The common mistake is a global kill switch: * { animation: none !important; transition: none !important; }. That removes decorative animation, yes — but it also removes the visual feedback that tells users a dropdown opened, a modal appeared, or a panel expanded. You've fixed one problem and created several others.
The spec is named "reduced" for a reason. Less motion. Not no motion.
What the Preference Is Actually For
The prefers-reduced-motion media query exists for users with vestibular disorders — conditions where certain types of motion (parallax scrolling, scaling, spinning, complex path animations) trigger dizziness or nausea. The W3C's guidance is clear: the goal is to reduce motion that can cause disorientation, not to eliminate all visual change.
There are two categories of animation in any UI:
Decorative animation — hover effects, ambient background motion, entrance animations that play automatically on page load, particle effects, parallax. These exist for aesthetics. They communicate nothing that isn't already communicated by the static design. When a user prefers reduced motion, remove these entirely.
Functional animation — state transitions (a toggle switching states), navigation feedback (a modal appearing), spatial orientation (a panel sliding in from the right to indicate it's a child of the current view), progress indication. These communicate information. Removing them entirely makes the UI harder to use, not easier. Keep them, but make them faster, simpler, and free of vestibular-triggering motion.
CSS Implementation
The correct pattern isn't a global reset. It's a targeted reduction:
/* Decorative: kill it entirely */
@media (prefers-reduced-motion: reduce) {
.hero-parallax,
.ambient-particles,
.auto-carousel {
animation: none;
transform: none;
}
}
/* Functional: keep it, but make it instant or near-instant */
.modal-overlay {
transition: opacity 250ms ease-out;
}
.modal-content {
transition: opacity 250ms ease-out, transform 250ms ease-out;
transform: translateY(16px);
}
.modal-content[data-open="true"] {
opacity: 1;
transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
.modal-overlay,
.modal-content {
/* Fade stays — it communicates appearance. Transform goes — it's vestibular. */
transition: opacity 150ms ease-out;
transform: none;
}
}
The modal still appears and disappears with a fade. The transform that moves it into place — which is the part that can cause disorientation — is removed. The user knows something happened. They just don't get the spatial motion.
Motion for React Implementation
In Motion for React, you can read the user preference with the useReducedMotion hook and adjust transition configs accordingly:
import { motion, useReducedMotion } from "motion/react";
function Panel({ isOpen }: { isOpen: boolean }) {
const reducedMotion = useReducedMotion();
const variants = {
open: {
opacity: 1,
x: 0,
transition: reducedMotion
? { duration: 0.01 } // functionally instant — state change still renders
: { type: "spring", stiffness: 300, damping: 30 },
},
closed: {
opacity: 0,
x: reducedMotion ? 0 : -20, // no spatial shift under reduced motion
},
};
return (
<motion.div animate={isOpen ? "open" : "closed"} variants={variants}>
{/* panel content */}
</motion.div>
);
}
The panel still transitions between open and closed states. The spring bounce and the spatial shift are gone. The opacity change at near-zero duration means screen readers and focus management still work correctly — the element is visible, then not.
The Difference Between Decorative and Functional in Practice
A button hover animation — color shift, slight scale — is decorative. The button communicates its purpose through its label and position. The hover animation is supplemental. Remove it.
A focus ring animation — the ring growing from the element's center to indicate keyboard focus — is functional. Remove the animation and the feedback still exists as a static ring. That's fine. Keep the ring, drop the entrance animation.
A route transition — a fade or slide between pages — sits in the middle. The transition communicates that navigation happened. A fade works without triggering vestibular issues. A horizontal slide can be disorienting for some users. Under reduced motion, fade is acceptable; slide is better removed.
Testing
Both Chrome and Safari DevTools let you emulate prefers-reduced-motion: reduce without changing your system settings. In Chrome: open DevTools, press Ctrl+Shift+P (or Cmd+Shift+P), type "Emulate CSS prefers-reduced-motion," and select "reduce." Run through your UI and check: does every state change still communicate clearly without motion? If anything becomes confusing or indistinguishable, that's a functional animation you removed that you shouldn't have.
The standard is not "does the site still load." It's "can a user with a vestibular disorder use every feature of this product without disorientation." That bar is higher, and meeting it means understanding the difference between animation as decoration and animation as communication.