EthicsComputerCourses
Join training +91 90532 10052

Motion & Reduced Motion

Web Accessibility · Lesson 17 of 28 Intermediate 6 min read Updated

Animation can make an interface feel alive. For some people it makes them ill. People with vestibular disorders, which affect the inner ear and balance, can feel dizziness, nausea or headaches from parallax scrolling, large zooming effects or backgrounds that slide. Moving text is hard to read for people with attention or reading difficulties, and flashing content can trigger seizures in people with photosensitive epilepsy.

In this lesson you will learn the WCAG rules about motion and flashing, use the prefers-reduced-motion media query in CSS and JavaScript, and build moving content that users can pause.

The rules

CriterionLevelRequirement
2.2.2 Pause, Stop, HideAMoving, blinking or scrolling content that starts automatically, lasts more than 5 seconds and appears alongside other content can be paused, stopped or hidden. The same applies to content that updates automatically, such as a news ticker.
2.3.1 Three Flashes or Below ThresholdANothing flashes more than three times in any one second, unless the flashes are below the general flash and red flash thresholds
2.3.3 Animation from InteractionsAAAMotion animation triggered by interaction can be turned off, unless it is essential

2.3.3 is level AAA, but it is easy to meet with one media query, so treat it as standard practice.

prefers-reduced-motion

Operating systems have a setting that asks apps to reduce motion. For example, Windows 11 has "Animation effects" under Accessibility, Visual effects; macOS and iOS have "Reduce motion"; and Android has "Remove animations" (the exact menu names vary by version and phone maker). Browsers expose this setting to CSS as the media feature prefers-reduced-motion, with two values: no-preference and reduce.

A safe pattern is to write animations only inside a no-preference block. Then users who asked for less motion never get them, and browsers that do not support the query get the calm version.

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Reduced Motion</title>
  <style>
    body { font: 1rem/1.5 system-ui, sans-serif; }
    .banner { padding: 1rem; background: #fff4e6; border-radius: 8px; }
    @media (prefers-reduced-motion: no-preference) {
      .banner { animation: slide-in 0.8s ease-out; }
      html { scroll-behavior: smooth; }
    }
    @media (prefers-reduced-motion: reduce) {
      .banner { animation: fade-in 0.3s; }
    }
    @keyframes slide-in { from { transform: translateX(-100%) scale(1.3); } to { transform: none; } }
    @keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
  </style>
</head>
<body>
  <div class="banner"><h1>Navratri Garba Night, Pune</h1><p>Saturday, 7 pm. Entry ₹300.</p></div>
  <p id="pref"></p>
  <button type="button" id="replay">Replay animation</button>
  <script>
    const query = matchMedia("(prefers-reduced-motion: reduce)");
    const show = () => {
      document.querySelector("#pref").textContent = query.matches
        ? "Your system asks for reduced motion: you see a short fade."
        : "No motion preference set: you see the slide-in.";
    };
    show();
    query.addEventListener("change", show);
    document.querySelector("#replay").addEventListener("click", () => {
      const banner = document.querySelector(".banner");
      banner.style.animation = "none";
      banner.offsetHeight; // force the browser to apply the change before restarting
      banner.style.animation = "";
    });
  </script>
</body>
</html>

To test without changing your system setting, open Chrome DevTools, open the Rendering panel from the More tools menu, and set "Emulate CSS media feature prefers-reduced-motion" to reduce. The script also listens for changes, so the message updates immediately.

"Reduce" does not have to mean "remove everything". A short fade or colour change is usually fine. What to remove is large movement: sliding, zooming, spinning, parallax and auto-scrolling.

Motion in JavaScript

Animations started by JavaScript, such as element.animate(), scroll effects or animated counters, ignore the CSS media query. Check it in script:

JavaScript
const reduceMotion = matchMedia("(prefers-reduced-motion: reduce)").matches;

document.querySelector("#top-link").addEventListener("click", (event) => {
  event.preventDefault();
  window.scrollTo({ top: 0, behavior: reduceMotion ? "auto" : "smooth" });
});

Moving content needs a pause button

Auto-rotating carousels, news tickers and moving banners fail 2.2.2 if they run longer than five seconds with no way to stop them. Users who need time to read, and screen reader users whose reading position keeps changing, are most affected. Give a visible pause button, stop moving when the user hovers or focuses inside, and do not auto-start when reduced motion is requested.

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Pausable Slides</title>
  <style>
    .slides { border: 2px solid #0b5cad; padding: 1rem; max-width: 26rem; }
  </style>
</head>
<body>
  <section class="slides" aria-labelledby="offers-title">
    <h2 id="offers-title">Today's offers</h2>
    <p id="slide">Slide 1 of 3: Saree sale, 30% off silk sarees.</p>
    <button type="button" id="pause" aria-pressed="false">Pause slideshow</button>
    <button type="button" id="next">Next offer</button>
  </section>
  <script>
    const slides = [
      "Saree sale, 30% off silk sarees.",
      "Free delivery in Lucknow on orders above ₹999.",
      "New arrivals: handloom cotton kurtas."
    ];
    let index = 0;
    let timer = null;
    const pauseBtn = document.querySelector("#pause");
    const show = () => {
      document.querySelector("#slide").textContent = `Slide ${index + 1} of 3: ${slides[index]}`;
    };
    const next = () => { index = (index + 1) % slides.length; show(); };
    const start = () => { if (!timer) timer = setInterval(next, 4000); };
    const stop = () => { clearInterval(timer); timer = null; };

    pauseBtn.addEventListener("click", () => {
      const paused = pauseBtn.getAttribute("aria-pressed") === "true";
      pauseBtn.setAttribute("aria-pressed", String(!paused));
      paused ? start() : stop();
    });
    document.querySelector("#next").addEventListener("click", next);

    const box = document.querySelector(".slides");
    box.addEventListener("focusin", stop);
    box.addEventListener("focusout", () => { if (pauseBtn.getAttribute("aria-pressed") === "false") start(); });

    if (matchMedia("(prefers-reduced-motion: reduce)").matches) {
      pauseBtn.setAttribute("aria-pressed", "true");
    } else {
      start();
    }
  </script>
</body>
</html>

The pause button is a toggle button: aria-pressed="true" makes screen readers announce it as "Pause slideshow, toggle button, pressed". Rotation stops while focus is inside the section, so keyboard users are not interrupted, and it never starts automatically for users who asked for reduced motion.

Flashing and autoplay video

  • Never make content flash more than three times per second. Avoid large flashing areas and strong red flashes altogether; the safe design is simply no flashing.
  • Do not autoplay video with sound. If a background video must play, keep it muted, short and provide a visible pause control.
  • Animated GIFs that loop forever also need a way to stop them, or should be replaced with a video that has controls.
HTML
<video src="garba-highlights.mp4" controls muted playsinline poster="garba.jpg">
  <track kind="captions" src="garba-highlights.en.vtt" srclang="en" label="English">
</video>

Common mistakes

  • Parallax and scroll-triggered zoom with no reduced-motion fallback.
  • Carousels that rotate with no pause button, or that restart while the user is reading.
  • Only checking reduced motion in CSS, while JavaScript animation libraries keep running.
  • scroll-behavior: smooth for everyone. Put it inside a no-preference block.
  • Flashing "SALE" banners or strobing effects.

Key points

  • Motion can cause dizziness and nausea for people with vestibular disorders.
  • Wrap large animations in @media (prefers-reduced-motion: no-preference); use calm alternatives for reduce.
  • Check matchMedia("(prefers-reduced-motion: reduce)") for animations started by JavaScript.
  • Auto-moving content lasting more than 5 seconds needs a way to pause, stop or hide it (2.2.2).
  • Never flash more than three times per second (2.3.1).

Exercise

A college homepage has a news ticker that scrolls headlines from right to left forever using a CSS animation, and a "Back to top" link with smooth scrolling. Write CSS that stops the ticker for users who prefer reduced motion and turns smooth scrolling on only for users without that preference. Also add a pause button for everyone.

Show solution
HTML
<div class="ticker" id="ticker">
  <p class="ticker-text">Admissions open for B.Sc. 2026 | Sports day on 14 November</p>
</div>
<button type="button" id="ticker-pause" aria-pressed="false">Pause news</button>

<style>
  @media (prefers-reduced-motion: no-preference) {
    html { scroll-behavior: smooth; }
    .ticker-text { animation: scroll-left 20s linear infinite; }
  }
  .ticker.paused .ticker-text { animation-play-state: paused; }
  @keyframes scroll-left { from { transform: translateX(100%); } to { transform: translateX(-100%); } }
</style>

<script>
  const btn = document.querySelector("#ticker-pause");
  btn.addEventListener("click", () => {
    const paused = document.querySelector("#ticker").classList.toggle("paused");
    btn.setAttribute("aria-pressed", String(paused));
  });
</script>

Users with reduced motion see the headlines as static text, everyone else can pause the ticker, and smooth scrolling applies only when no preference is set.

Quick Quiz

1. Which media query value means the user asked for less motion?

2. A carousel rotates automatically every 4 seconds forever. Which criterion requires a pause control?

3. Why check matchMedia in JavaScript?

4. What is the WCAG limit for flashing content?

Learn Web Accessibility with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call