Reduced Motion Support: prefers-reduced-motion Done Properly

Was this helpful?

The production symptom: support tickets that read like “your site makes me nauseous,” “scrolling feels like an earthquake,” or “the login page is fighting me.” These aren’t “design preferences.” They’re reliability bugs for humans.

If you ship animations without a real reduced-motion strategy, you’re rolling dice with accessibility, conversion, and incident response. The good news: prefers-reduced-motion is one of the rare web platform features that’s both humane and operationally cheap—if you implement it with discipline.

What prefers-reduced-motion really is (and what it isn’t)

prefers-reduced-motion is a user preference exposed to the web platform via CSS media queries and JavaScript APIs. It’s a signal that the user wants less motion. Less motion can mean fewer transitions, no parallax, no autoplaying animations, no “scroll-jacking,” and no “helpful” smooth scrolling that drags the page under their cursor like it’s being towed.

It is not:

  • A request to make your UI ugly.
  • A suggestion to reduce frame rate and keep the same animation (that can feel worse).
  • A place to dump every “we don’t want to debug this” fallback. Reduced motion is not “reduced quality.”

Think of motion like a production dependency. It can fail. It can overload a client device. And it can harm users. Supporting reduced motion is your circuit breaker.

The operational reality: motion bugs are distributed systems bugs

Animations don’t run in a vacuum. They interact with:

  • CPU/GPU scheduling (especially on integrated graphics and low-power modes)
  • input devices (trackpads, wheel mice, touch)
  • layout and paint performance (which you can absolutely DoS yourself with)
  • framework lifecycle (React/Angular/Vue rerenders vs. animation state)
  • third-party widgets (ads, chat, analytics, A/B testing)

When reduced motion is enabled, your system is effectively in a different runtime mode. Treat it as such: test it, monitor it, and keep it from drifting.

One quote I keep coming back to, because it applies directly here: “Hope is not a strategy.” — Gene Kranz

(Your animation strategy should not be “hope users don’t notice.” They do.)

Historical context and interesting facts

  • OS-level “reduce motion” settings predate the web feature. Platforms added motion reduction largely for accessibility and vestibular comfort; the web later gained a standardized hook.
  • prefers-reduced-motion is a Media Queries Level 5 feature. It’s part of the modern wave of “user preference” signals alongside light/dark color scheme.
  • The signal is typically binary-ish, but implementation varies. You’ll usually see reduce vs no-preference; some environments may behave differently in embedded contexts.
  • “Smooth scrolling” became a mainstream default surprisingly fast. Designers loved it; some users felt physically ill. The spec-level fix was not “ban it” but “respect preference.”
  • Vestibular disorders aren’t rare edge cases. Motion sensitivity can come from migraines, inner ear conditions, concussions, medications, or plain aging. Your user base is not a demo of 25-year-old motion graphics enthusiasts.
  • Parallax is a frequent offender. It couples scroll input to layered movement; for some users it feels like the world is slipping under them.
  • Autoplaying animations are often worse than transitions. They can be relentless, especially when they loop in peripheral vision.
  • Performance and accessibility are entangled here. Janky animation (dropped frames) can trigger discomfort more than smooth motion, so “just let it stutter” is not a solution.
  • Reduced motion can improve business metrics. Not because motion is “bad,” but because removing distractions and nausea tends to reduce bounce and rage clicks.

Joke #1: Animations are like interns—great when supervised, catastrophic when left to “express themselves” in production.

Definition of done: reduced motion that won’t regress

Most teams “support reduced motion” by sprinkling a couple of CSS rules in a corner and calling it a day. That’s like adding a firewall rule and declaring the company secure.

Here’s a practical definition of done that survives redesigns and framework migrations:

1) You have a single source of truth for the preference

CSS can read it with media queries, JS can read it with matchMedia. But your app should not have five different “isReducedMotion” utilities that disagree.

2) You’ve classified motion, not just turned it off

Not all motion is equal. Categorize:

  • Essential: communicates state change (e.g., focus ring, subtle opacity changes) and can be shortened rather than removed.
  • Helpful: improves comprehension (e.g., a short expand/collapse transition). For reduce mode, shorten, remove overshoot, remove bounce.
  • Decorative: background loops, confetti, parallax, “breathing” cards. For reduce mode, disable.

3) Reduced motion works without JS

Start with CSS, then refine with JS. If JS fails, reduced motion should still be honored for the majority of effects.

4) Motion settings are tested as a first-class variant

Not “someone checked it once on a MacBook.” You want:

  • visual regression coverage for reduce mode
  • unit tests around your motion utilities
  • an explicit QA step in release sign-off

5) Third-party animation sources are controlled

Lottie, marketing embeds, chat widgets, analytics overlays—these are the usual culprits. You need a policy: either they honor reduced motion, or they don’t ship.

6) You can explain what happens on toggle

Users can change the OS setting while your app is open. Your code should respond. If your animations only check on startup, you’ve built a “works in demos” feature.

Fast diagnosis playbook

You’re on-call (or you’re pretending not to be). A ticket arrives: “Reduced motion is on, but the site still animates.” Or worse: “Reduced motion is on and the site is now broken.” Here’s how to find the bottleneck quickly.

First: confirm the preference is actually “reduce” in the environment

  • Is the OS setting enabled?
  • Is the browser exposing it (not in some weird embedded webview mode)?
  • Is your code reading it correctly?

Second: identify where motion is coming from

  • CSS transitions/animations
  • JS-driven animations (requestAnimationFrame loops)
  • scroll behavior (CSS smooth scrolling or JS scroll libraries)
  • canvas/webgl loops
  • embedded iframes or third-party scripts

Third: check whether “reduce” is implemented as “disable” or “shorten”

If you set durations to 0ms globally, you can create new bugs: focus jumps, layout thrash, event timing issues, “animationend” listeners never firing, etc.

Fourth: verify runtime updates

Toggle the OS setting while the app is open. If nothing changes, you likely have a stale value cached at module load.

Fifth: check for regressions introduced by optimization

Common offenders: “performance improvements” that moved animation logic into a shared utility that no longer reads the preference, or CSS refactors that replaced safe properties with layout-triggering ones.

Implementation patterns: CSS, JS, and component systems

CSS: the baseline you should always have

Start with a policy that applies across the app. A typical baseline:

  • disable long-running decorative animations
  • remove or reduce transitions (especially transform-based “fly in” motion)
  • disable smooth scrolling

The CSS mechanism is straightforward:

  • @media (prefers-reduced-motion: reduce) for reduce mode
  • @media (prefers-reduced-motion: no-preference) for the default

But “straightforward” is where bugs breed. You need to know what to disable, and where global rules backfire:

The global reset trap

You’ve seen this snippet:

  • set animation-duration: 0.001ms !important
  • set transition-duration: 0.001ms !important

It’s popular because it’s easy. It’s also dangerous because:

  • It breaks components that rely on transition timing for state cleanup.
  • It can cause sudden jumps that feel worse than a short fade.
  • It masks motion sources in dev because everything “sort of works.”

Do a targeted approach: define motion tokens (durations/easings) and swap them based on preference. Disable only truly decorative loops globally.

JS: read the preference once, then listen for changes

Use window.matchMedia('(prefers-reduced-motion: reduce)'). But don’t do it in ten files. Wrap it.

Important detail: browsers have changed APIs over time; some environments support addEventListener('change', ...), older ones use addListener. Your wrapper should handle both.

Component frameworks: avoid “motion as a side effect”

In React and friends, motion often becomes a side effect: a hook triggers a transition on mount, a library animates layout changes, a “micro-interaction” triggers on hover.

Two guidelines that keep you out of trouble:

  1. Make motion parameters explicit props. Duration, easing, and whether motion is enabled shouldn’t be hardcoded deep in a component.
  2. Never infer reduced motion from “device performance.” Users request reduced motion for comfort, not because their GPU is tired.

Scroll and navigation: treat smooth scroll like a power tool

CSS scroll-behavior: smooth is tempting because it’s one line. It’s also one of the easiest ways to violate reduced motion.

Policy:

  • Default: allow smooth scroll only for explicit user-triggered navigation (e.g., clicking “Jump to section”), not for arbitrary scroll events.
  • Reduce mode: disable smooth scroll.

Canvas/WebGL: “reduced motion” might mean “pause”

If you run an always-on render loop, you should treat reduce mode as a hint to stop or throttle. Replace background motion with a static frame, or render on-demand. Bonus: battery life improves, your laptop fan stops trying to take off, and nobody files a ticket about dizziness.

Joke #2: If your loading spinner needs reduced motion support, congratulations—you’ve invented a new kind of stress test for humans.

Practical tasks with commands (and decisions)

These are hands-on tasks you can run today. Each one includes: a command, an example output, what it means, and what decision you make from it.

Task 1: Find CSS reduced-motion coverage in your repo

cr0x@server:~$ rg -n "prefers-reduced-motion" .
./src/styles/motion.css:12:@media (prefers-reduced-motion: reduce) {
./src/components/Carousel/carousel.css:88:@media (prefers-reduced-motion: reduce) {

Meaning: You have two explicit reduced-motion blocks. That might be great—or it might mean most components ignore it.

Decision: If fewer than your core motion sources appear here, plan a motion audit (see checklist). Reduced motion should be systematic, not incidental.

Task 2: Inventory CSS animations and transitions

cr0x@server:~$ rg -n "(animation:|animation-|transition:|transition-)" src
src/styles/base.css:41:transition: all 300ms ease;
src/styles/toast.css:9:animation: slideIn 450ms cubic-bezier(.2,.8,.2,1);
src/components/Hero/hero.css:22:animation: float 4s ease-in-out infinite;

Meaning: You have at least one infinite animation (float) and at least one dangerous transition: all.

Decision: Infinite decorative animations must be disabled in reduce mode. Replace transition: all with explicit properties (opacity/transform), then gate durations via motion tokens.

Task 3: Catch “scroll-behavior: smooth” landmines

cr0x@server:~$ rg -n "scroll-behavior:\s*smooth" src
src/styles/base.css:7:html { scroll-behavior: smooth; }

Meaning: Smooth scrolling is global. That’s a classic reduced-motion violation.

Decision: Change to conditional: default off or scoped. In reduce mode, force scroll-behavior: auto.

Task 4: Locate requestAnimationFrame loops that may run forever

cr0x@server:~$ rg -n "requestAnimationFrame\(" src
src/visuals/background.ts:55:rafId = requestAnimationFrame(tick);
src/components/Chart/liveChart.ts:112:window.requestAnimationFrame(draw);

Meaning: There are JS-driven animations/render loops.

Decision: Ensure these loops respect reduced motion: pause, render once, or switch to event-driven rendering. Also ensure cleanup on unmount to avoid leaks.

Task 5: Find Framer Motion / animation library defaults

cr0x@server:~$ rg -n "(framer-motion|useReducedMotion|motion\.)" src
src/app/App.tsx:14:import { MotionConfig } from "framer-motion";
src/components/Modal/Modal.tsx:6:import { motion, useReducedMotion } from "framer-motion";

Meaning: You’re using a library that has opinions about motion. Good—if configured centrally.

Decision: Verify a global config exists (e.g., MotionConfig) and that component-level overrides don’t re-enable motion in reduce mode.

Task 6: Detect Lottie usage (often ignores reduced motion by default)

cr0x@server:~$ rg -n "(lottie|bodymovin)" src
src/components/EmptyState/EmptyState.tsx:3:import Lottie from "lottie-react";
src/components/PromoBanner/PromoBanner.tsx:8:import lottieData from "./promo.json";

Meaning: Lottie animations are in the product UI, likely looping.

Decision: In reduce mode: don’t autoplay; show a static frame or alternate image. Treat this as required, not “nice to have.”

Task 7: Verify your design tokens include motion tokens

cr0x@server:~$ rg -n "(--duration|--easing|motion)" src/styles
src/styles/tokens.css:12:--duration-fast: 120ms;
src/styles/tokens.css:13:--duration-medium: 220ms;
src/styles/tokens.css:14:--duration-slow: 360ms;

Meaning: You have motion-related CSS variables. Great foundation.

Decision: Add reduced-motion overrides for these variables instead of trying to zero everything with !important. Example: shorten, and remove bounce easings.

Task 8: Confirm Playwright tests can emulate reduced motion

cr0x@server:~$ rg -n "reducedMotion" tests
tests/e2e/login.spec.ts:9:  await page.emulateMedia({ reducedMotion: "reduce" });

Meaning: At least one test uses reduced motion emulation.

Decision: Expand coverage to motion-heavy pages. Add a test that asserts no infinite animations and no smooth scrolling behavior in reduce mode (as best you can verify via DOM/CSS).

Task 9: Quick check your bundle for smooth scroll polyfills

cr0x@server:~$ rg -n "(smoothscroll|scrollTo\(\{|behavior:\s*\"smooth\")" src
src/lib/navigation.ts:28:window.scrollTo({ top: 0, behavior: "smooth" });

Meaning: JS is forcing smooth scroll, regardless of user setting.

Decision: Gate this call: if reduced motion is enabled, use behavior: "auto" or omit behavior.

Task 10: Identify “transitionend/animationend” event dependencies

cr0x@server:~$ rg -n "(transitionend|animationend)" src
src/components/Drawer/Drawer.tsx:88:el.addEventListener("transitionend", onDone);

Meaning: Component logic depends on transitions completing.

Decision: In reduced motion mode, you may shorten durations so much that events behave differently, or you may disable transitions entirely. Add an explicit code path: if reduced motion is enabled, call onDone() immediately (or after a microtask), don’t wait for events that may never fire.

Task 11: Confirm third-party scripts that might animate overlays

cr0x@server:~$ ls -1 public/vendor
chat-widget.js
marketing-overlay.js

Meaning: Vendor scripts exist and may inject animations outside your CSS control.

Decision: Audit vendors. If they don’t support reduced motion, wrap them: disable autoplay, suppress overlays, or load alternate configurations when reduce mode is on.

Task 12: Local verification in Chromium via DevTools (emulation is not reality, but it helps)

cr0x@server:~$ chromium --user-data-dir=/tmp/chrome-prm --enable-features=WebContentsForceDark
[12874:12874:1229/101512.116955:INFO:chrome_main_delegate.cc(594)] Started

Meaning: You launched a clean Chromium profile to avoid extension noise. (The flag shown isn’t about motion; the point is: isolate variables.)

Decision: In DevTools Rendering panel, emulate “prefers-reduced-motion” and visually inspect key flows. Then repeat with actual OS reduce motion enabled to catch discrepancies.

Task 13: Smoke-test that you’re not globally forcing “no-preference” in CSS

cr0x@server:~$ rg -n "prefers-reduced-motion:\s*no-preference" src
src/styles/motion.css:33:@media (prefers-reduced-motion: no-preference) {

Meaning: You have explicit styling for the default mode. That’s fine.

Decision: Ensure your “no-preference” block doesn’t accidentally override reduce mode due to specificity. Prefer a “reduce” override that wins (and test it).

Task 14: Check for CSS animations that might run even when hidden

cr0x@server:~$ rg -n "infinite" src/styles src/components
src/components/Hero/hero.css:22:animation: float 4s ease-in-out infinite;
src/components/Background/bg.css:11:animation: shimmer 1.6s linear infinite;

Meaning: Infinite loops exist. Shimmer effects on skeleton screens are a common offender.

Decision: In reduce mode, turn shimmer into a static placeholder. For hero floats: freeze. Your CPU (and your users) will thank you.

Three corporate mini-stories from the trenches

Mini-story 1: The incident caused by a wrong assumption

A mid-sized B2B SaaS team shipped a redesign with a slick new sidebar. The drawer slid in, the background blurred, and the overlay faded in. They also had a reduced motion toggle—technically. It was implemented with a global CSS snippet that set animation durations to nearly zero in reduce mode.

On paper, it looked compliant. In practice, the drawer component listened for transitionend to move focus into the drawer and to set aria-hidden correctly on the background content. With the duration effectively zero, the event didn’t consistently fire across browsers. Sometimes it fired before the listener attached. Sometimes not at all.

Users with reduced motion enabled started reporting “keyboard navigation broken” and “page is stuck.” Support escalated it as an accessibility outage. Engineering initially treated it as a minor edge case because it “only happens with reduced motion on.” That sentence aged badly in about an hour.

The fix was boring and correct: the drawer’s state machine stopped relying on transition events as the source of truth. Transitions became cosmetic. If reduced motion is enabled, it runs a deterministic path: set DOM state, set focus, skip animations, and call cleanup synchronously.

The wrong assumption wasn’t about CSS. It was the belief that reduced motion is “just shorter durations.” It’s not. It’s a distinct runtime mode.

Mini-story 2: The optimization that backfired

A large consumer product team had performance issues on low-end devices. Their solution: replace several CSS transitions with a JavaScript animation orchestrator so they could “batch updates” and “avoid layout thrash.” They also consolidated a bunch of micro-interactions into one shared animation timeline.

Performance improved in benchmarks. Real-world reports got worse. The orchestrator used requestAnimationFrame loops that ran continuously for “responsiveness,” even when the animations were idle. It also defaulted to smooth scrolling when the user navigated within a page. Reduced motion support was implemented in CSS only, so none of the JS animation logic respected it.

Users with reduced motion enabled still saw parallax and scroll easing. Some also experienced increased battery drain because the continuous rAF loop kept the page “hot.” The team blamed the OS. Then the browser. Then “maybe users misconfigured something.” Classic.

They fixed it by doing something that felt like going backwards: they moved most interactions back to CSS (where the platform can optimize), and they built a single JS “motion preference” module that gated every animation entry point. They also changed the rAF loop to be event-driven: only run while an animation is active.

The backfire wasn’t that JS animations are always bad. It was that the optimization ignored the operational requirement: reduced motion must be enforced consistently across all motion sources.

Mini-story 3: The boring but correct practice that saved the day

A finance-adjacent web app had a strict release process. Not glamorous. The team maintained a small “accessibility contract” checklist for every UI change. One line item: “Test with reduced motion enabled on at least one OS, and in automated E2E for motion-heavy flows.”

A designer proposed a new onboarding flow with animated illustrations and a scrolling “guided tour.” Engineering implemented it using a popular animation library and a couple of Lottie files. During pre-release, QA ran the reduced motion checklist and found that the guided tour still auto-scrolled between steps, even when reduced motion was enabled.

The fix was simple: in reduce mode, the guided tour stopped auto-advancing and replaced scroll transitions with instant jumps plus clear focus management. Lottie animations were replaced with static frames. No drama, no incident, no public apology tour.

Nothing about this was clever. The practice worked because it was repeatable, enforced, and attached to releases like a seatbelt. You don’t notice it until you really needed it.

Common mistakes (symptoms → root cause → fix)

1) Symptom: “Reduced motion enabled, but parallax still happens”

Root cause: Parallax is implemented in JS scroll handlers (or a library) that never checks the preference.

Fix: Gate the parallax initialization on reduced motion. In reduce mode, render a static background and remove scroll listeners.

2) Symptom: “Reduced motion makes modals/drawers break (focus stuck, overlay weird)”

Root cause: State changes rely on transitionend/animationend events.

Fix: Make component state deterministic; treat animations as optional. In reduce mode, call completion handlers directly and manage focus synchronously.

3) Symptom: “Everything snaps instantly and feels worse”

Root cause: Global “duration to 0ms” reset. Sudden jumps can be disorienting.

Fix: Shorten rather than eliminate for essential transitions; eliminate only decorative motion. Use motion tokens and adjust easings.

4) Symptom: “Skeleton loaders shimmer forever even in reduce mode”

Root cause: Infinite CSS animation not overridden in reduce mode.

Fix: Replace shimmer with a static placeholder for reduce mode. Consider using a subtle color block without movement.

5) Symptom: “Smooth scrolling still triggers in reduce mode”

Root cause: scroll-behavior: smooth set globally, or JS scrollTo({behavior:"smooth"}) called unconditionally.

Fix: In reduce mode force scroll-behavior: auto, and gate JS scrolling behavior by preference.

6) Symptom: “Reduced motion works on page load but not after OS toggle”

Root cause: Preference read once and cached; no listener for changes.

Fix: Subscribe to media query changes and update a centralized store; re-render motion configs accordingly.

7) Symptom: “Third-party marketing banner keeps animating”

Root cause: Vendor widget ignores reduced motion and injects its own CSS/JS.

Fix: Load a reduced-motion vendor config, or suppress the widget in reduce mode. Make this a procurement requirement.

8) Symptom: “CPU usage stays high even when nothing is happening”

Root cause: Idle rAF loop, canvas render loop, or animated background running continuously.

Fix: Stop rendering when not needed; in reduce mode default to paused/static. Ensure cleanup on unmount.

9) Symptom: “Reduced motion breaks analytics or A/B tests”

Root cause: Experiment code depends on animation timings, or it measures events tied to transitions.

Fix: Use explicit state events rather than animation events. Instrument business events independent of motion.

Checklists / step-by-step plan

Step 1: Do a motion inventory (one afternoon, high ROI)

  1. Search for CSS animations/transitions and list components that use them.
  2. Search for rAF usage and scroll handlers.
  3. List third-party widgets that draw on the screen (ads, chat, marketing overlays).
  4. Classify each motion item as essential/helpful/decorative.

Step 2: Establish motion tokens (so you can change behavior without a repo-wide hunt)

  1. Create duration tokens (fast/medium/slow) and easing tokens (standard/emphasized).
  2. Replace hardcoded durations and “transition: all” with tokens and explicit properties.
  3. In reduce mode, shorten durations and remove bounce/overshoot easings.

Step 3: Implement reduced motion in CSS first

  1. Disable infinite decorative animations.
  2. Disable smooth scrolling.
  3. Ensure key interactions still convey state (focus rings, selection changes).

Step 4: Implement reduced motion in JS with a single preference module

  1. Wrap matchMedia in a utility that exposes a current value and a subscription mechanism.
  2. Gate initialization of parallax, autoplay animations, and render loops.
  3. Handle runtime toggles by updating your app state (or reload motion configs).

Step 5: Make components resilient to “no animation”

  1. Remove reliance on transitionend/animationend for correctness.
  2. Ensure focus management and ARIA state updates happen deterministically.
  3. Verify that removing animations doesn’t change layout unexpectedly.

Step 6: Add automated tests (or your future self will re-live this bug)

  1. Add E2E coverage with reduced motion emulation for key flows (auth, checkout, onboarding).
  2. Add regression tests that assert major motion sources are disabled (e.g., no infinite CSS animations on critical pages).
  3. Add a lint rule or CI check to flag scroll-behavior: smooth and “transition: all” unless explicitly approved.

Step 7: Operationalize it

  1. Add reduced motion verification to release sign-off.
  2. Document your “motion policy” in the repo (short, enforced, updated).
  3. Ensure third-party vendors are reviewed for reduced motion support.

FAQ

Q1: Is reduced motion just for people with disabilities?

No. Motion sensitivity can be situational or temporary: migraines, medication, concussion recovery, vertigo, fatigue, even just being on a bumpy train. Build for humans, not labels.

Q2: Should we disable all transitions when reduced motion is enabled?

Disable decorative motion and infinite loops. For essential transitions, prefer short, subtle changes (often opacity) rather than dramatic movement. “No movement” is the goal, not “no feedback.”

Q3: Is “duration: 0ms” a valid strategy?

It’s a blunt instrument. It can break components that depend on animation events or timing, and it can create harsh snapping. Use tokens and explicit logic paths for correctness.

Q4: How do we handle smooth scrolling?

Disable smooth scroll in reduce mode. Also avoid globally enabling it. If you must use it for a jump link, gate the behavior in JS and keep it user-initiated.

Q5: What about micro-interactions like hover effects?

Hover effects can stay if they’re subtle and non-moving (color/opacity). Avoid hover-driven translations or rotations in reduce mode, especially on dense UIs where hover happens constantly.

Q6: Do we need to respond to preference changes while the app is open?

Yes. Users can toggle OS settings without restarting your tab. Listen for media query changes and update your motion configuration.

Q7: How do we deal with animation libraries?

Pick libraries that expose a reduced-motion mode or accept parameters. Configure centrally. Then enforce a rule: no component should override reduced-motion behavior to re-enable motion.

Q8: What’s the quickest way to catch reduced motion regressions?

Add a smoke E2E test that loads a motion-heavy page with reduced motion emulated and asserts key elements do not animate (or that animation classes are absent). Pair with a manual OS-level check in release QA.

Q9: If reduced motion is on, can we still use spinners?

Yes, but prefer non-rotational indicators: progress bars, dots that appear without movement, or static “Loading…” text. If you keep a spinner, stop it in reduce mode or switch to a subtle fade.

Conclusion: next steps you can do this week

Reduced motion support isn’t a “polish” item. It’s a runtime mode that affects correctness, performance, and user trust. Implement it like you implement any operational requirement: centrally, testably, and with a plan for third-party chaos.

Practical next steps:

  1. Run the repo searches above and build a motion inventory.
  2. Kill global smooth scrolling and infinite decorative animations in reduce mode.
  3. Implement a single JS preference module and wire it to your animation entry points.
  4. Fix components that rely on animation events for correctness.
  5. Add at least one E2E reduced-motion test for your top user flow.
  6. Add a release checklist line item so this doesn’t quietly regress next quarter.

If you do only one thing: stop treating motion as decoration. In production, motion is behavior. Behavior needs controls.

← Previous
MySQL vs MariaDB: WooCommerce checkout lag—one setting fixes it, the other just masks it
Next →
Proxmox VM won’t start after changing CPU type: recovery steps that work

Leave a comment