Overview
Motion on the web can trigger vestibular disorders, seizures, migraines, and nausea for a significant number of users. Both macOS and Windows provide a system-level “reduce motion” preference, exposed to CSS via prefers-reduced-motion. Respecting this preference is not optional — it is a WCAG requirement. Beyond the media query, providing on-page pause controls ensures users without access to system settings can still stop distracting animations.
WCAG Criteria:
- 2.3.3 Animation from Interactions — motion triggered by interaction can be disabled, unless essential
- 2.2.2 Pause, Stop, Hide — any auto-updating or moving content must be pausable
Key requirements:
- Use the additive approach: only add animation when
prefers-reduced-motion: no-preference— this means animation is off by default and only enabled for users who have not requested reduction - The subtractive approach (adding animation, then removing it in
prefers-reduced-motion: reduce) is fragile — if the media query fails or is unsupported, users still get animation - Always provide an on-page pause/play button in addition to the media query, since not all users know how to change system settings
- View Transitions API and CSS
transitionshould also respect this query - Consider reducing motion rather than removing it entirely — a crossfade instead of a slide, for example
Animation Preference
Respecting Reduced Motion vs. Ignoring Preferences
Inaccessible
<style>
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-30px); }
}
</style>
<!-- Always animates — no respect for user preferences -->
<div style="width: 60px; height: 60px; background: #7c3aed;
border-radius: 12px;
animation: bounce 0.8s ease-in-out infinite;">
</div>
<!-- No pause button. No prefers-reduced-motion query. -->Live Preview
This box bounces continuously. No way to pause it. Ignores prefers-reduced-motion.
Accessible
<style>
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-30px); }
}
/* ADDITIVE approach: animation only added
when user has NOT requested reduction */
@media (prefers-reduced-motion: no-preference) {
.animated-box.is-playing {
animation: bounce 0.8s ease-in-out infinite;
}
}
</style>
<div class="animated-box is-playing" id="animated-box"
style="width: 60px; height: 60px; background: #7c3aed;
border-radius: 12px;">
</div>
<!-- On-page pause control for users without system settings -->
<button aria-pressed="true" onclick="
var box = document.getElementById('animated-box');
var playing = box.classList.contains('is-playing');
if (playing) {
box.classList.remove('is-playing');
this.textContent = 'Play';
this.setAttribute('aria-pressed', 'false');
} else {
box.classList.add('is-playing');
this.textContent = 'Pause';
this.setAttribute('aria-pressed', 'true');
}
">Pause</button>Live Preview
Animation respects prefers-reduced-motion. Use the button to pause/play.
What’s wrong with the bad example?
- The animation runs unconditionally with no way to stop it
- The
prefers-reduced-motionmedia query is completely ignored — users who have requested reduced motion in their operating system still see the bouncing box - There is no pause button, so even users who have not configured system-level preferences have no way to stop the distracting motion
- This can trigger vestibular symptoms (dizziness, nausea) in affected users
Why the additive approach is better:
- The additive approach (
prefers-reduced-motion: no-preference) starts with no animation and only adds it for users who haven’t requested reduction - If the media query is unsupported in an older browser, the element simply doesn’t animate — a safe default
- The subtractive approach (adding animation normally, then removing it in
prefers-reduced-motion: reduce) fails dangerously: if the query isn’t supported, animation still plays for everyone - The on-page pause button provides a second layer of control for users who don’t know about system settings
What the user experiences:
| Version | Default behavior | With reduced motion enabled |
|---|---|---|
| Bad (no media query) | Bounces continuously | Still bounces continuously |
| Good (additive approach) | Bounces, with Pause button | No animation at all |