Guides · · Maxwell Prehoda
14 Prefers-Reduced-Motion Examples: How Apple, GitHub and Real Sites Tone Motion Down
`prefers-reduced-motion` is a CSS media feature that is true when someone has turned on Reduce Motion (macOS, iOS), Remove animations (Android) or turned off animation effects (Windows). The best implementations do not delete every animation when it is on. They swap large, spatial motion such as slides, zooms, parallax and spins for gentle changes such as fades, and keep anything that carries meaning.
Below are fourteen real examples of how to do that: what Apple's own Reduce Motion setting changes, how GitHub and the Animal Crossing site honour it, the CSS patterns from web.dev, CSS-Tricks and Tatiana Mac, the React and GSAP tools, the WCAG rule behind it all, and how the snippets in our own directories ship with a guard built in. Every claim links to the page we checked.
1. Apple iOS Reduce Motion: dissolve instead of zoom: Best for deciding what your reduced version should look like
Platform: iOS and iPadOS system setting
The clearest model for a reduced-motion mode is the one Apple ships. With Settings > Accessibility > Motion > Reduce Motion on, Apple says screen transitions and effects use the dissolve effect instead of zoom or slide effects, the parallax that shifts wallpaper, apps and alerts as you tilt the device is disabled, and animations in certain apps, such as Weather, are turned off.
Notice what Apple does not do: it does not make transitions instant. You still get a short crossfade, so you can tell one screen replaced another. That is the pattern to copy on the web: keep the change visible, remove the travel.
Our Fade In below is a pure-CSS version of that dissolve, an opacity change with no movement. It is a good reduced-motion replacement for almost any slide or zoom entrance.
Watch out for: A dissolve is still an animation. If your reduced branch fades a full-screen element over a second, it can still be uncomfortable. Keep replacement fades short, around 100 to 200 ms.
2. The Animal Crossing site: swap, do not strip: Best for marketing sites with heavy hero animation
Platform: Website (Nintendo), documented by Val Head
Val Head's write-up of the Animal Crossing site is the best documented real-world example. The site respects `prefers-reduced-motion` automatically and also asks first-time visitors whether they want reduced motion, remembers the choice, and lets them switch back.
In reduced mode it replaces the hero's spinning motion-blur transition with a crossfade, removes the confetti from the character carousel and turns large entrance movements lower on the page into opacity fades. Small hover movements, opacity fades and colour fades stay exactly as they were. Head names apple.com and viljamisdesign.com as using the same approach.
Our Fade In Blur below is a pure-CSS entrance in the same spirit: opacity and blur change, and nothing moves at all.
Watch out for: A site-level toggle is a nice addition, not a replacement for the media query. Visitors who already set the preference in their OS should never have to find your toggle.
3. GitHub: animated images sync with the system: Best for apps that show user-uploaded GIFs
Platform: GitHub.com, Settings > Accessibility > Motion
Reduced motion is not only about your own CSS. GitHub added an option in May 2022 to stop animated images like GIFs from playing automatically, and its accessibility settings now offer Sync with system, Enabled or Disabled for autoplaying animated images. By default, GitHub's docs say, it follows your system-level reduced-motion preference.
That is the right default for any product with user content: issues, chat, comments, docs. Other people's GIFs are the motion you control least, and a looping GIF in a long thread is exactly the kind of uncontrolled motion that causes trouble.
The settings pattern is worth copying too: default to the OS, then offer an explicit override in both directions.
Watch out for: An image stopped with JavaScript after it loads may still play for a moment. Where you can, serve a static frame first and load the animated version only when motion is allowed.
4. web.dev: opt in with no-preference, and more than CSS: Best for learning every place the preference can be read
Platform: web.dev article by Thomas Steiner
Thomas Steiner's web.dev article covers more ground than most tutorials. In CSS you can disable motion inside `@media (prefers-reduced-motion: reduce)`, or add it only inside `no-preference`. You can put all animation CSS in a separate stylesheet with a `media` attribute so people who opted out never download it. In JavaScript, `window.matchMedia('(prefers-reduced-motion: reduce)')` gives you the value, and you should listen for its `change` event because JavaScript animations do not update by themselves.
Two less known tricks: a `picture` element can serve an animated AVIF, WebP or GIF only when the media condition allows motion, with a still image as the fallback, and the `Sec-CH-Prefers-Reduced-Motion` client hint lets a server pick the right assets at request time.
Steiner's summary of the goal is the one to keep: minimise movement, preferably to the point where all non-essential movement is removed.
Watch out for: Matching the query once at page load is a common bug. People toggle the setting while a tab is open, especially when they feel sick, so subscribe to changes.
5. Tatiana Mac: the no-motion-first approach: Best for new projects and design systems starting clean
Platform: Essay, tatianamac.com (2021)
Tatiana Mac flips the usual pattern. Instead of writing animations and then switching them off for `reduce`, she sets `animation: none` in the base styles and applies the animation only inside `@media (prefers-reduced-motion: no-preference)`. The question that decides it: is this animation critical to understanding the content? If not, it is an enhancement, and enhancements are opt-in.
Her reasoning has two parts. Browsers that do not understand the query get no animation, which is the safe failure. And it changes how a team thinks: motion becomes something you add on purpose, which is easier to document in a design system.
She also adds a caveat worth remembering: `no-preference` does not mean someone consented to motion. They may simply not know the setting exists, so still keep motion purposeful.
Watch out for: No-motion-first is harder to retrofit onto a large existing codebase, because every animation has to move inside a query. For existing sites, a well-built global reset is often the practical first step.
6. WCAG 2.3.3: what counts as motion: Best for knowing which effects the rule actually covers
Platform: W3C WCAG 2.2, Success Criterion 2.3.3 (Level AAA)
Success Criterion 2.3.3, Animation from Interactions, says motion animation triggered by interaction can be disabled unless the animation is essential. It is Level AAA, so most legal standards do not require it, but it is the clearest definition of what reduced motion should cover. Its first sufficient technique, C39, is using the CSS `prefers-reduced-motion` query.
The useful part is the definition: motion animation is adding steps between states to create the illusion of movement. Colour, blur and opacity changes do not count unless they change an element's perceived size, shape or position. That is the line between what to remove and what you can keep.
Our Glow Pulse below animates only a box-shadow halo in the accent colour. It draws the eye to a button without moving it, the kind of emphasis that sits on the safe side of that line.
Watch out for: Autoplaying loops are a separate rule. WCAG 2.2.2 (Level A) still requires a way to pause anything that moves for more than five seconds alongside other content, whatever the user's motion setting.
7. Val Head on A List Apart: the motion that triggers symptoms: Best for spotting which of your animations to change first
Platform: A List Apart article (2015, updated 2020)
Not all animation is equally risky, and Val Head's article lists the patterns most likely to cause dizziness or nausea for people with vestibular disorders: large-scale movement that covers much of the screen, exaggerated parallax where foreground and background move at very different speeds, scrolljacking that is not tied to scroll speed, motion in a different direction from the scroll, fast travel through large virtual space, and automatic motion with no way to stop it.
She notes that animation limited to opacity, colour or blur is unlikely to cause problems, and that small elements with 3D rotation are less of a concern than full-screen ones. That gives you a priority list: fix parallax, full-screen transitions and autoplaying carousels before you worry about a button hover.
Our Parallax Starfield below is a pure-CSS example of the layered, different-speed motion on her list. Its copyable CSS includes a reduced-motion guard that stops it, which is exactly what a background like this should do.
Watch out for: Parallax and scroll-linked effects are often built in JavaScript or with scroll-driven animations, where duration-based CSS resets are not a reliable off switch. Check those separately with the preference turned on.
8. Eric Bailey's reset on CSS-Tricks: Best for retrofitting reduced motion onto an existing site
Platform: CSS-Tricks article (2019)
Eric Bailey's "Revisiting prefers-reduced-motion" popularised the global reset: under `reduce`, set every element's animation and transition duration to a tiny value with `!important`. The comment thread refined it into the version most sites use: `animation-duration: 0.01s`, `animation-iteration-count: 1`, `transition-duration: 0s` and `scroll-behavior: auto`, all with `!important`, applied to every element and its pseudo-elements.
Two details make it work. A tiny duration instead of `animation: none` means `animationend` and `transitionend` still fire, so JavaScript that waits for them does not break. And the iteration count matters: without it, an infinite loop at 0.01 s would flicker extremely fast instead of stopping.
Bailey frames the reset as deliberate friction: it shows up on every inspected element and makes developers think about motion.
Watch out for: A blanket reset also kills motion that carries meaning, like a spinner showing work is happening or a progress fill. Exempt those with a class and give them a slower, calmer version instead.
9. Josh W. Comeau's usePrefersReducedMotion hook: Best for React apps with JavaScript-driven animation
Platform: React hook, joshwcomeau.com
CSS media queries do nothing for animations driven from JavaScript. Josh Comeau's article gives a small React hook, `usePrefersReducedMotion`, that reads the media query, stores the result in state, subscribes to changes so it updates when someone flips the OS checkbox, and cleans up the listener on unmount.
For CSS he recommends the opt-in direction, writing animations inside `no-preference`, so the default is calm and browsers that ignore the query get no animation.
The SSR note is the part people miss. On the server the hook cannot know the preference, so his server-safe version starts with animations disabled and turns them on after the client mounts. That is safe for people who need reduced motion, at the cost of skipping animations that would play immediately on mount.
Watch out for: If you copy only the simple version into a Next.js or other server-rendered app, the first render can disagree with the client and animations may flash. Use the SSR variant he provides.
10. Motion: MotionConfig reducedMotion="user": Best for React apps already using Motion (Framer Motion)
Platform: Motion for React, free and open source
If your animations run through Motion, one prop does most of the work. Wrap the app in `MotionConfig` with `reducedMotion="user"` and, according to Motion's docs, every `motion` component automatically disables transform and layout animations for people with Reduce Motion on, while keeping animations of other values like `opacity` and `backgroundColor`.
That split matches the WCAG definition almost exactly: movement goes, colour and fades stay. For everything else there is the `useReducedMotion` hook, which returns a boolean you can use in any logic, for example to stop a background video from autoplaying.
It is the least effort, most correct option for a React codebase, because it covers components written by people who forgot to think about it.
Watch out for: It only covers `motion` components. CSS animations, canvas, video and third-party widgets in the same app still need their own handling.
11. GSAP: gsap.matchMedia() with a reduceMotion condition: Best for GSAP timelines and ScrollTrigger sites
Platform: GSAP, free
GSAP's `gsap.matchMedia()` lets you define conditions, including `reduceMotion: "(prefers-reduced-motion: reduce)"`, and read them inside one setup function. The docs' own example sets `duration: reduceMotion ? 0 : 2`, so reduced-motion users get the end state instantly.
The feature that makes it safe is automatic cleanup: every animation and ScrollTrigger created inside the function is reverted when a condition stops matching, and the function runs again whenever any condition toggles. Someone who turns on Reduce Motion mid-visit gets the calm version without a reload.
For scroll-heavy GSAP sites, the right reduced branch often skips the scroll-linked choreography entirely and simply shows the content.
Watch out for: Setting duration to 0 on a ScrollTrigger scrub does not remove the scroll-linked movement. For scroll-driven effects, do not create the trigger at all in the reduced branch.
12. MDN: where people turn it on, and how to test: Best for testing your reduced-motion branch before shipping
Platform: MDN reference, Baseline widely available since January 2020
MDN's page is the quickest reference for where the preference lives: Windows 11 under Accessibility > Visual Effects > Animation Effects, macOS under Accessibility (Display on older versions, Motion from macOS 26), iOS under Accessibility > Motion, Android 9 and later under Accessibility > Remove animations, plus GNOME and KDE. The feature has been available across browsers since January 2020.
For testing, Firefox lets you set `ui.prefersReducedMotion` to `1` in `about:config`, which takes effect immediately, and Chromium DevTools can emulate the media feature from the Rendering panel. Test the whole page, not just a component.
MDN's own example also models good behaviour: in the reduce branch it swaps a pulsing animation for a slow dissolve instead of removing it.
Watch out for: `@media (prefers-reduced-motion)` with no value means `reduce`. Writing it bare is valid but easy to misread in code review; spell out the value.
13. Our snippets: a guard in every copy: Best for copy-paste animation that is safe by default
Platform: Motions directory, free, pure CSS Ours
Every snippet you copy from our directories, about 1,800 of them, comes with a `prefers-reduced-motion: reduce` block appended for you. For hover effects, text animations, buttons, toggles, backgrounds, keyframes and the rest, it follows the reset pattern above, scoped to the snippet: `animation-duration: 0.01ms`, `animation-iteration-count: 1` and `transition-duration: 0.01ms`, all `!important`, on the snippet's root, its children and their pseudo-elements. Animations jump to their end state and loops stop after one pass.
Because the guard is scoped to the snippet's own class, it does not touch the rest of your page, and it lives in the same block of CSS you paste, so nobody has to remember to add it.
The Aurora background below is a typical case: a slow ambient loop for most visitors, and a still image for anyone who asked for less motion.
Watch out for: Jumping to the end state is right for decoration, but check that the end state makes sense on its own. An entrance that ends visible is fine; a loop whose last keyframe is mid-gesture may look odd frozen.
14. Our loaders: slower, not stopped: Best for loading states that must keep signalling progress
Platform: Motions directory, free, pure CSS Ours
Loaders are the exception to "jump to the end". A spinner frozen on one frame looks like a crash, and a loader is information: the app is still working. So the guard on our loaders keeps them moving but sets `animation-duration: 3s` under `reduce`, turning a quick spin or pulse into a slow, calm one.
It is the same principle Apple and the Animal Crossing site follow: reduce the intensity, keep the meaning. A three-second cycle is gentle enough for most people and still clearly alive.
Our Ripple Loader below expands two rings outward like drops on water. With Reduce Motion on, the same rings expand at a slow, three-second pace.
Watch out for: Slowing a loader that scales or rotates a large area still leaves large motion on screen. For full-screen loaders, consider a reduced version that only fades or shows a static progress label.
How to choose your reduced-motion pattern
There is no single right switch. Decide per animation, using three questions.
Match it to what the motion means
Decoration (parallax, ambient backgrounds, confetti): remove it. Spatial transitions (slides, zooms, page changes): replace with a short crossfade, as iOS does. Status (spinners, progress, typing indicators): keep it, slower and smaller, like our loaders. Feedback that is colour or opacity only: usually keep it, since WCAG 2.3.3 does not count it as motion.
Match it to your stack
Plain CSS: a scoped reset or no-motion-first media queries. React with Motion: MotionConfig reducedMotion="user" plus useReducedMotion for video and canvas. Other React: Josh Comeau's hook, SSR variant if you render on the server. GSAP: gsap.matchMedia() with a reduceMotion condition. User content: sync GIF autoplay with the system, like GitHub.
Match it to the risk
Fix the patterns Val Head lists first: full-screen movement, parallax, scrolljacking, motion against the scroll direction and autoplaying carousels. Then give long loops a pause control regardless of the setting (WCAG 2.2.2). Small hover lifts and colour fades come last, and often need no change at all.
Turn on Reduce Motion and use your site for five minutes
The fastest audit is the real one. Turn the setting on in your OS (or `ui.prefersReducedMotion` in Firefox), then click through your main flows. Note every slide, zoom, parallax layer and autoplaying loop that still moves, and every place that broke because an animation never finished. Fix the large motion first and replace it with a fade where the change still needs to be visible.
If you want animation that already behaves, every snippet in our directory ships with a scoped guard. For something custom, describe it to our creator and say how the reduced version should behave; it is part of the brief, not an afterthought.
Make a hero entrance animation in pure CSS keyframes: the headline rises 24px and fades in over 600 ms on cubic-bezier(0.22, 1, 0.36, 1), then the subhead follows 120 ms later. Add a slow ambient gradient drift behind it on a 12 s loop. Under prefers-reduced-motion: reduce, replace both rises with a 150 ms opacity fade, and stop the gradient on a still frame. Keep animationend firing in both modes.
Common questions
What does prefers-reduced-motion do?
It is a CSS media feature that reports whether someone has asked their operating system to minimise motion, for example Reduce Motion on macOS and iOS, Remove animations on Android, or turning off animation effects on Windows. Sites read it with @media (prefers-reduced-motion: reduce) in CSS or window.matchMedia in JavaScript and tone down their animations.
Should reduced motion remove all animations?
No. Remove or replace large, spatial motion such as slides, zooms, parallax and spins, but keep changes that carry meaning, like a crossfade between screens or a slowed-down loading indicator. Apple's own Reduce Motion swaps zoom and slide transitions for a dissolve rather than making them instant.
Is prefers-reduced-motion required by WCAG?
Not directly. WCAG 2.3.3 Animation from Interactions, which says interaction-triggered motion can be disabled unless essential, is Level AAA, and using prefers-reduced-motion is one of its sufficient techniques (C39). WCAG 2.2.2 Pause, Stop, Hide, which is Level A, separately requires a way to pause content that moves for more than five seconds.
How do I test prefers-reduced-motion?
Turn on the setting in your OS, or in Firefox set ui.prefersReducedMotion to 1 in about:config, which applies immediately. Chromium DevTools can also emulate the media feature from its Rendering panel. Test full user flows, since JavaScript and scroll-driven animations are often missed by CSS-only fixes.
Why use 0.01ms instead of animation: none?
A near-zero duration lets animations finish instantly while still firing animationend and transitionend events, so JavaScript that waits for them keeps working. Pair it with animation-iteration-count: 1, or infinite loops will flicker rapidly instead of stopping.
Sources
- MDN: prefers-reduced-motion
- web.dev: prefers-reduced-motion (Thomas Steiner)
- W3C: Understanding SC 2.3.3 Animation from Interactions
- Apple Support: Reduce screen motion on iPhone or iPad
- Val Head: Reduced Motion in the Wild
- Val Head, A List Apart: Designing Safer Web Animation for Motion Sensitivity
- GitHub Docs: Managing accessibility settings
- Eric Bailey, CSS-Tricks: Revisiting prefers-reduced-motion
- Tatiana Mac: No-motion-first
- Josh W. Comeau: Accessible Animations in React
- Motion: React accessibility
- GSAP: gsap.matchMedia()