prefers-reduced-motion
Honor prefers-reduced-motion — cut decorative animation, keep essential feedback, and wire CSS and JS motion to one preference.
- css
- prefers-reduced-motion
- a11y
prefers-reduced-motion tells you the user asked for less motion — vestibular disorders, migraines, preference, or battery. Decorative parallax, auto-playing carousels, and looping logo spins should yield. Essential state changes can stay as instant or near-instant updates.
Docs: MDN prefers-reduced-motion, WCAG animation from interactions, web.dev reduced motion.
CSS kill switch (targeted)
.hero {
animation: float 6s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
.hero {
animation: none;
}
.drawer {
transition: none;
}
}
Global nuclear option used by some resets:
@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;
}
}
Nuclear works, but can break intentional opacity fades that aid comprehension — prefer layering reduced alternatives for product UI:
.toast {
animation: slide-in 200ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
.toast {
animation: fade-in 120ms linear;
}
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
JavaScript must listen too
CSS alone won’t stop element.animate(), rAF loops, or Lottie.
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)");
function scrollToId(id) {
document.getElementById(id)?.scrollIntoView({
behavior: reduce.matches ? "auto" : "smooth",
});
}
reduce.addEventListener("change", () => {
// tear down parallax listeners, pause videos, etc.
});
What to reduce vs keep
| Reduce / remove | Keep (usually) |
|---|---|
| Parallax, ken burns, large page transitions | Instant show/hide of dialogs |
| Infinite decorative loops | Progress indicators that don’t thrash position |
| Auto-advancing carousels | User-initiated carousels with pause |
| Scroll-jacking | Normal scrolling |
If motion conveys meaning, provide a non-motion equivalent (text, icon state).
Scroll and view transitions
html {
scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
}
Same for View Transitions API — skip or use a short crossfade only when reduced motion is off.
Interview out-loud
“prefers-reduced-motion: reduce means I strip or shorten decorative animation in CSS and disable smooth scrolling / WAAPI loops in JS. I listen to matchMedia change events. Essential feedback can remain as opacity or instant state. Ignoring this preference is an a11y bug in motion-heavy UIs.”
Footguns
- CSS-only fix while JS keeps animating.
- Removing all feedback so users can’t tell a click registered.
- Marketing pages with no pause on autoplay video backgrounds.
- Testing only with OS setting off.
scroll-behavior: smoothonhtmlwithout a reduce override.
Design system token
:root {
--motion-distance: 0.5rem;
--motion-duration: 200ms;
}
@media (prefers-reduced-motion: reduce) {
:root {
--motion-distance: 0;
--motion-duration: 0.01ms;
}
}
.toast {
transform: translateY(var(--motion-distance));
transition: transform var(--motion-duration) ease, opacity var(--motion-duration) ease;
}
Centralizing duration/distance tokens keeps hundreds of components honest without pasting the media query everywhere. Still audit third-party widgets (carousels, Lottie, confetti) separately — they ignore your tokens.
QA steps
- Enable reduced motion in OS settings.
- Walk marketing home, modal open/close, route transitions, skeleton loaders.
- Confirm no infinite animations remain.
- Confirm focus and state changes still visible.
- Re-disable and ensure motion returns (matchMedia change handler).
Related
- CSS animations keyframes
- CSS transitions
- Reduced motion preferences (a11y)
- Scroll-driven animations intro
Further reading
Related guides
- outline vs border for FocusKeep focus visible with outline or box-shadow — why border shifts layout, :focus-visible patterns, and never outline: none alone.
- Accent Color and Form ControlsUse accent-color to theme native checkboxes, radios, ranges, and progress — what it paints, what it skips, and when to custom-style instead.
- Auto-fit vs Auto-fillauto-fit collapses empty tracks; auto-fill keeps them. Use both with minmax for responsive card grids without media-query soup.
- BEM Naming MethodologyBlock, Element, Modifier naming for CSS — how it limits specificity wars, what to name, and when utility or CSS modules beat strict BEM.
- blend-mode Basicsmix-blend-mode vs background-blend-mode — how pixels combine, isolation, and the stacking/readability traps in product UI.