Detect the OS accessibility setting live, simulate both modes in-page, and compare removing animation vs replacing motion with fades.
Your browser reports the real OS setting via matchMedia('(prefers-reduced-motion: reduce)'). Use the preview toggle to simulate either mode without changing system settings.
Card slides + scales in with a parallax background. Under reduced motion, movement is replaced by an opacity cross-fade — feedback stays, vestibular triggers go.
Full motion: slide + scale. Reduced: calm fade only.
Stripping all transitions leaves users with zero feedback. Swapping slide for fade preserves "something changed" without simulating self-motion.
Under reduce: transition: none. Element appears instantly with no indication of change.
Under reduce: opacity cross-fade only. Position/scale motion removed, feedback preserved.
/* Accessible default — motion is additive */ .modal { opacity: 1; } @media (prefers-reduced-motion: no-preference) { .modal { animation: modal-in 220ms ease-out; } @keyframes modal-in { from { opacity: 0; transform: translateY(8px) scale(0.98); } to { opacity: 1; transform: none; } } } @media (prefers-reduced-motion: reduce) { .modal { transition: opacity 200ms ease; } }
/* Safety net for legacy / third-party CSS */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { /* 0.01ms keeps transitionend firing */ animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } }
Use opt-in for new components; keep a global reset as insurance. In JS, mirror the query with matchMedia and listen for change.