Motion Preference Support Matrix: Browsers & Operating Systems
Motion preference media queries are only as reliable as the chain that feeds them: an operating-system toggle, a browser engine that reads it, and a CSS cascade that acts on it. This reference collects the shipped versions, OS settings, and per-engine guard recipes for prefers-reduced-motion, prefers-reduced-transparency, and the update media query, so that the override patterns from the prefers-reduced-motion implementation guide can be deployed with engine-accurate expectations. It is part of the wider Accessibility & Inclusive Motion Standards section.
Pages in this section
Syntax reference
Three media features carry motion-relevant preference signals from the operating system into CSS. Each is a discrete feature with a small closed set of values:
| Media feature | Values | Boolean context matches when | Signal meaning |
|---|---|---|---|
prefers-reduced-motion |
no-preference, reduce |
reduce |
User asked the OS to minimize non-essential motion |
prefers-reduced-transparency |
no-preference, reduce |
reduce |
User asked the OS to reduce translucent/blurred surfaces |
update |
none, slow, fast |
slow or fast |
How quickly the output device can re-render after a style change |
Two syntax details matter in practice. First, the boolean form @media (prefers-reduced-motion) — no value — matches whenever the value is anything other than no-preference. It behaves identically to (prefers-reduced-motion: reduce) today because reduce is the only other value, but writing the value explicitly is clearer and future-proof. Second, an engine that does not recognize a media feature treats the whole query as not all: the block simply never applies. Media queries fail closed, which is the foundation of every guard recipe below.
update is the odd one out: it describes the device, not the user. update: fast means the device can animate smoothly (any conventional screen); update: slow covers displays like e-ink where reflows are visible and expensive; update: none covers paged media such as print. A scroll-driven animation on an update: slow device is wasted work at best and a smeared mess at worst, so the feature belongs in the same defensive stack as the preference queries even though its semantics differ.
Minimal working example
The reference pattern that this entire matrix supports: motion is declared as an enhancement gated on no-preference, so every failure mode — old browser, unsupported feature, preference set — resolves to the static state:
/* Baseline: final visual state, no motion, works everywhere */
.reveal {
opacity: 1;
transform: none;
}
/* Enhancement: only browsers that BOTH support scroll timelines
AND report no motion preference get the animation */
@supports (animation-timeline: view()) {
@media (prefers-reduced-motion: no-preference) and (update: fast) {
.reveal {
opacity: 0;
transform: translateY(1.5rem);
animation: reveal-up linear both;
animation-timeline: view();
animation-range: entry 0% entry 60%;
}
}
}
@keyframes reveal-up {
to { opacity: 1; transform: none; }
}
Because the animated state lives inside the guarded block, there is nothing to undo when the preference is active: no !important, no override race, no orphaned animation-timeline value. This is the inverted version of the override-based approach documented in the step-by-step reduced-motion override patterns, and the two compose: gate new motion behind no-preference, and keep a global reduce override as a safety net for legacy styles.
Support matrix: prefers-reduced-motion
prefers-reduced-motion is the safest media feature in this reference — it has shipped in every major engine for years and can be used without any support detection:
| Platform / browser | Chrome / Edge | Safari | Firefox |
|---|---|---|---|
| Desktop, first shipped | Chrome 74 (2019), Edge 79 (2020) | Safari 10.1 (2017) | Firefox 63 (2018) |
| macOS | Reads Reduce Motion | Reads Reduce Motion | Reads Reduce Motion |
| Windows | Reads animation effects setting | — | Reads animation effects setting |
| Linux | Reads desktop animation setting (GTK) | — | Reads GTK setting; ui.prefersReducedMotion pref overrides |
| iOS / iPadOS | WebKit shell — follows Safari | Safari on iOS 10.3+ reads Reduce Motion | WebKit shell — follows Safari |
| Android | Chrome 74+ reads Remove animations | — | Firefox for Android reads the same system flag |
Notes that the table cannot carry:
- iOS browsers are all WebKit. Chrome, Edge, and Firefox on iOS and iPadOS wrap the system WebKit view, so
prefers-reduced-motionbehavior there is Safari’s behavior regardless of the browser badge. - Android’s signal is the animator scale. The Accessibility → “Remove animations” toggle zeroes the system animation duration scales, and Chromium maps a zeroed animator scale to
reduce. Turning the three animation scales off in Developer Options has the same effect in practice — worth knowing because a test device with developer settings tweaked can reportreduceunexpectedly. - Live updates work everywhere. All current engines re-evaluate the query and fire
MediaQueryListchangeevents when the OS toggle flips mid-session; no reload is required. JavaScript that caches the value must subscribe to those events, as covered in detecting prefers-reduced-motion with matchMedia.
Support matrix: prefers-reduced-transparency
prefers-reduced-transparency matters for scroll-driven interfaces that animate backdrop-filter glass panels or translucent sticky headers. Support is far narrower:
| Aspect | Chrome / Edge | Safari | Firefox |
|---|---|---|---|
| Shipped version | Chrome 118 (2023), Edge 118 | Not shipped; treat as unsupported and design the fallback accordingly | Behind layout.css.prefers-reduced-transparency.enabled pref; not shipped by default |
| macOS source | Reduce Transparency | — | Reads the setting only with the pref enabled |
| Windows source | Transparency effects: off | — | Same pref caveat |
| iOS source | — (WebKit shell) | — | — |
| Android source | No dedicated OS toggle exposed | — | — |
The deployment consequence: prefers-reduced-transparency: reduce currently reaches Chromium users only. That is fine — the query fails closed elsewhere — but it means transparency reduction cannot be your only accommodation. Pair it with a design that keeps text on opaque or near-opaque surfaces by default, and treat the media query as an extra hardening layer where it exists:
.sticky-header {
/* Baseline: readable without any transparency support signal */
background: rgb(20 22 26 / 0.85);
backdrop-filter: blur(12px);
}
@media (prefers-reduced-transparency: reduce) {
.sticky-header {
background: rgb(20 22 26); /* fully opaque */
backdrop-filter: none;
}
}
Do not conflate the two preferences. A user with vestibular sensitivity may enable Reduce Motion and leave transparency on; a user with low vision or contrast sensitivity may do the opposite. The OS exposes them as independent toggles, and CSS must honor them independently.
Support matrix: the update media query
The update feature shipped later than prefers-reduced-motion but is now available across all three engines:
| Aspect | Chrome / Edge | Safari | Firefox |
|---|---|---|---|
| Shipped version | Chrome 113 (2023), Edge 113 | Safari 17 (2023) | Firefox 102 (2022) |
| Typical value on desktop/mobile | fast |
fast |
fast |
slow reported by |
E-ink class devices (browser-dependent) | E-ink class devices (browser-dependent) | E-ink class devices (browser-dependent) |
none reported by |
Print / paged output | Print / paged output | Print / paged output |
In practice, virtually every phone, laptop, and desktop reports update: fast, so the query’s day-to-day value is defensive: it lets you express “this animation only makes sense on a display that can actually animate.” Because engines that predate the feature ignore the whole query, always use it in the enhancement direction — @media (update: fast) { /* add motion */ } — never as a slow-only override that legacy browsers would miss.
Operating-system toggles that drive the media queries
Each OS exposes the motion preference in a different place, and knowing the exact path is essential both for real-device testing and for support documentation:
| OS | Setting path | Drives |
|---|---|---|
| macOS | System Settings → Accessibility → Display → Reduce motion | prefers-reduced-motion in all macOS browsers |
| macOS | System Settings → Accessibility → Display → Reduce transparency | prefers-reduced-transparency (Chromium; Safari does not ship the query) |
| Windows 11 | Settings → Accessibility → Visual effects → Animation effects: off | prefers-reduced-motion in Chromium and Firefox |
| Windows 10 | Settings → Ease of Access → Display → Show animations in Windows: off | prefers-reduced-motion |
| Windows 11 | Settings → Personalization → Colors → Transparency effects: off | prefers-reduced-transparency (Chromium) |
| iOS / iPadOS | Settings → Accessibility → Motion → Reduce Motion | prefers-reduced-motion in Safari and every WebKit-shell browser |
| Android 9+ | Settings → Accessibility → Remove animations | prefers-reduced-motion in Chrome and Firefox for Android |
| Linux (GNOME) | GNOME Tweaks → General → Animations: off (or gtk-enable-animations) |
prefers-reduced-motion in Chromium and Firefox |
The flow is always the same, as the diagram above shows: the toggle writes an OS-level accessibility flag, the browser engine reads that flag through the platform accessibility API, and every open document’s media queries are re-evaluated. The CSS cascade then applies or removes the guarded blocks on the next style recalculation. Nothing about this round-trip involves JavaScript — which is exactly why CSS-first reduced-motion handling is more robust than script-driven approaches that can race hydration.
Two OS-level behaviors are worth flagging. On iOS, enabling Reduce Motion also changes system behaviors (crossfade instead of zoom for app transitions), so user expectations on that platform are anchored to replacement, not removal — a crossfade fallback reads as native there. On Android, because the signal is the animator duration scale rather than a dedicated web flag, OEM battery savers or performance profiles that zero the animator scale can flip prefers-reduced-motion to reduce without the user ever visiting accessibility settings. Treat unexplained reduce values on Android hardware as plausible, not as test-environment corruption.
Guard recipes per engine
Because all three motion-related features fail closed, the guard strategy is about choosing the direction of each gate. These recipes cover the combinations that come up in scroll-driven work.
Recipe 1 — universal reduced-motion override (all engines, no detection needed):
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
animation-timeline: auto !important; /* detaches scroll()/view() timelines */
}
}
Safe everywhere: engines without animation-timeline simply ignore that one declaration and apply the rest. This is the safety net; component-level recipes refine it.
Recipe 2 — enhancement gate for scroll-driven animations (Chromium today, other engines as they ship):
@supports (animation-timeline: scroll()) {
@media (prefers-reduced-motion: no-preference) {
.progress-bar {
animation: grow linear;
animation-timeline: scroll(root);
}
}
}
The nesting order is deliberate. @supports asks “does this engine have scroll timelines at all?” — which currently means Chrome/Edge 115+, with Firefox behind a pref and Safari support in active development — and @media asks “does this user want the motion?” Only when both answer yes does the animation exist. The broader cascade discipline behind this pattern is covered in browser support and progressive enhancement, with feature-specific variants in the @supports guard recipes for animation-timeline.
Recipe 3 — transparency hardening (Chromium-only effect, harmless elsewhere):
@media (prefers-reduced-motion: reduce), (prefers-reduced-transparency: reduce) {
.glass-panel {
backdrop-filter: none;
background: var(--surface-opaque);
}
}
The comma is an OR: each query in the list is evaluated independently, so an engine that has never heard of prefers-reduced-transparency still honors the prefers-reduced-motion half. This is the one place where the fail-closed rule has a subtlety — an unknown feature kills its own query, not its siblings in the list. Had the two been joined with and, the whole condition would be dead in Safari and Firefox.
Recipe 4 — JavaScript feature detection for the newer queries:
// A recognized media feature never serializes to 'not all'
const supportsTransparencyQuery =
window.matchMedia('(prefers-reduced-transparency: reduce)').media !== 'not all';
const reducedMotion =
window.matchMedia('(prefers-reduced-motion: reduce)').matches;
matches alone cannot distinguish “supported, value is no-preference” from “not supported at all” — both return false. The media !== 'not all' check makes the distinction when analytics or conditional logic need it.
DevTools emulation: how it works per engine
Emulation replaces the OS flag at the media-query-evaluation stage — the dashed box in the diagram — which makes it fast but also means it exercises less of the chain than a real toggle.
Chrome / Edge: DevTools → Command Menu (Ctrl/Cmd+Shift+P) → “Show Rendering” → Emulate CSS media feature prefers-reduced-motion → reduce. The override applies to that tab only, persists while DevTools is open, and fires MediaQueryList change events exactly as a real OS flip would, so JavaScript listeners are exercised too. The same Rendering tab exposes prefers-reduced-transparency emulation in current Chromium.
Firefox: there is no Rendering-tab equivalent for motion. Set ui.prefersReducedMotion in about:config — an integer pref where 1 means reduce and 0 means no-preference. The pref overrides whatever the OS reports and applies browser-wide until removed. Delete the pref to return to OS-driven behavior.
Safari: no per-tab emulation is exposed; flip macOS Reduce Motion in System Settings and the change propagates to open pages immediately. Because that is a real OS-level flip, Safari testing doubles as an end-to-end verification of the full chain — the one thing emulation cannot give you.
Emulation’s blind spot is worth stating plainly: it confirms that your CSS responds to the media query, but it cannot confirm that the browser correctly reads your users’ OS settings, that WebKit-shell browsers on iOS inherit the flag, or that an Android animator-scale quirk behaves as expected. The full device-by-device workflow, including automated coverage, is documented in testing prefers-reduced-motion across operating systems and browsers.
Gotchas and failure modes
-
Gating overrides on
reduceinstead of gating motion onno-preference. An override-style block only helps users whose browsers support the query — which forprefers-reduced-motionis everyone, but forprefers-reduced-transparencyis Chromium only. When the accommodation is critical, invert the gate so the accommodating state is the default. -
and-combining a new feature with an old one.@media (prefers-reduced-motion: reduce) and (prefers-reduced-transparency: reduce)evaluates tonot allin any engine missing either feature. Use comma-separated lists when you mean OR, and remember each comma-separated query lives or dies alone. -
Assuming iOS Chrome is Chromium. Support tables listing “Chrome 118+” for
prefers-reduced-transparencydo not apply to Chrome on iOS, which is WebKit underneath. Version-gated expectations must be keyed to the engine, not the product name. -
Stale
matchMediasnapshots. Reading.matchesonce at module scope and never subscribing tochangeevents leaves an SPA blind to mid-session OS toggles. Every engine fires the event; code just has to listen. -
Android developer-option side effects. A device with animator scales set to off in Developer Options reports
reduce. QA devices frequently ship in this state, producing “animations are broken on Android” bug reports that are actually the media query working correctly. -
Zero-duration event loss. The universal override uses
0.01ms, not0ms, because some engines skipanimationendfor zero-duration animations, silently hanging any JavaScript awaiting completion. Keep the floor at0.01msin every recipe. -
Forgetting
updateon unusual hardware. Kiosk builds and e-reader browsers may reportupdate: slow. Scroll-driven effects there degrade badly; the(update: fast)gate in the minimal example costs one line and removes the entire class of problem.
Audit checklist
- Confirm every decorative animation is gated behind
(prefers-reduced-motion: no-preference)or covered by the globalreduceoverride — no motion should exist outside one of the two. - Verify
animation-timeline: autoappears in the override path for every scroll-driven element; theanimationshorthand does not reset it. - Check comma vs
andin every media query list that mentionsprefers-reduced-transparencyorupdate— unknown features must not take down sibling queries. - Emulate
reducein the Chromium Rendering tab and rundocument.getAnimations().filter(a => a.playState !== 'idle')— expect an empty array. - Set
ui.prefersReducedMotionto1in Firefox and confirm the same result, since Firefox has no Rendering-tab toggle. - Flip macOS Reduce Motion with Safari open to verify live re-evaluation end-to-end.
- Test one real Android device via Settings → Accessibility → Remove animations, and one iOS device via Settings → Accessibility → Motion.
- Record the minimum supported engine versions from the tables above in your project’s support policy so future media-feature additions are checked against the same baseline.
Related
- Motion Scaling & User Preferences — continuous motion reduction with custom properties for users who want less motion, not none
- Vestibular-Safe Motion Design Patterns — the design-side rules that decide which animations need gating in the first place
- WCAG 2.2 Animation Compliance Criteria — how the media queries in this matrix map onto testable success criteria
- Fallback Strategies for Legacy Browsers — what to serve engines that predate scroll timelines entirely