Clean Markup

Designing Inclusive Web Animations with prefers-reduced-motion

Designing Inclusive Web Animations with prefers-reduced-motion

Parallax scrolling, zooming cards, and automatic smooth-scroll jumps can trigger motion sickness, vertigo, and migraines for users with vestibular disorders. Modern operating systems—including macOS, iOS, Windows 10, and Android—allow users to enable a system-wide "Reduce motion" preference, which browsers expose directly to CSS and JavaScript via the prefers-reduced-motion media query.

Respecting reduced motion does not mean removing every visual transition on the page. Color, border, and opacity fades are completely safe; the goal is to eliminate spatial movement (transform: translate(), scale(), rotate()), keyframe loops, and scroll-behavior: smooth when the user requests calmer motion.

Example

/* Opt-in approach: enable smooth scrolling only when the user has no motion restriction */
@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}

/* Defensive reset: strip spatial animations when reduced motion is active */
@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;
  }
}

Input : Interactive UI card with hover elevation, slide-in entrance animation, and smooth anchor scrolling
Output : Full spatial animation for default users and instant or opacity-only transitions for users with prefers-reduced-motion: reduce.

Implementation

.feature-card {
  padding: 24px;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  background-color: #ffffff;
  /* Safe default transition: color and border changes do not trigger vestibular discomfort */
  transition: border-color 200ms ease, box-shadow 200ms ease, opacity 200ms ease;
}

.feature-card:hover,
.feature-card:focus-within {
  border-color: #04ae92;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
}

/* Layer spatial movement strictly inside no-preference */
@media (prefers-reduced-motion: no-preference) {
  .feature-card {
    transition: transform 250ms cubic-bezier(0.22, 1, 0.36, 1),
      border-color 200ms ease,
      box-shadow 200ms ease;
  }

  .feature-card:hover,
  .feature-card:focus-within {
    transform: translateY(-6px);
  }
}
// Checking the user's motion preference in JavaScript before running requestAnimationFrame or Web Animations
const motionQuery = window.matchMedia("(prefers-reduced-motion: reduce)");

function scrollToSection(targetSelector) {
  const target = document.querySelector(targetSelector);
  if (!target) return;

  target.scrollIntoView({
    behavior: motionQuery.matches ? "auto" : "smooth",
    block: "start"
  });
}

// Listen for live OS-level changes without requiring a page reload
motionQuery.addEventListener("change", () => {
  if (motionQuery.matches) {
    document.documentElement.classList.add("reduced-motion");
  } else {
    document.documentElement.classList.remove("reduced-motion");
  }
});

Main Point: Treat spatial motion (transform, parallax, and scroll-behavior: smooth) as a progressive enhancement inside @media (prefers-reduced-motion: no-preference) while keeping safe opacity and color states accessible to everyone.

Credits

Photo by Lorenzo Herrera

← Home