[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"quiz-prefers-reduced-motion-respect-the-setting":44,"search-suggestions":60,"quiz-article-prefers-reduced-motion-respect-the-setting":107},[4,20,32],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,{"id":45,"slug":46,"kind":47,"title":48,"description":49,"config":50,"verticalId":5,"vertical":55,"course":52,"_count":56,"access":57,"attempts":59,"questionCount":51},"01a106cb-f8ca-72be-ac89-ec62e6e48153","prefers-reduced-motion-respect-the-setting","PRACTICE_QUIZ","prefers-reduced-motion — test yourself","Eight questions on the CSS media feature for motion sensitivity: its values, the fail-closed pattern, where it stops working, and what it can't tell you.",{"questionCount":51,"timeLimitSec":52,"shuffleQuestions":18,"shuffleOptions":17,"negativeMarking":19,"passScorePct":53,"maxAttempts":52,"revealAnswers":54,"allowFlagging":18,"allowBacktracking":17},8,null,70,"AFTER_SUBMIT",{"slug":6,"name":7},{"questions":51},{"allowed":17,"reason":58},"FREE",[],[61,65,69,73,77,81,85,89,93,97,100,104],{"slug":62,"name":63,"articles":64},"webdev","Webdev",129,{"slug":66,"name":67,"articles":68},"javascript","Javascript",106,{"slug":70,"name":71,"articles":72},"frontend","Frontend",81,{"slug":74,"name":75,"articles":76},"tutorial","Tutorial",49,{"slug":78,"name":79,"articles":80},"css","Css",41,{"slug":82,"name":83,"articles":84},"typescript","Typescript",18,{"slug":86,"name":87,"articles":88},"performance","Performance",16,{"slug":90,"name":91,"articles":92},"react","React",15,{"slug":94,"name":95,"articles":96},"browser","Browser",12,{"slug":98,"name":99,"articles":96},"node","Node",{"slug":101,"name":102,"articles":103},"html","Html",9,{"slug":105,"name":106,"articles":103},"accessibility","Accessibility",{"id":108,"slug":46,"title":109,"subtitle":52,"excerpt":110,"coverUrl":111,"locale":13,"readingMinutes":112,"publishedAt":113,"viewCount":114,"likeCount":19,"commentCount":19,"author":115,"vertical":120,"topic":121,"tags":123,"_count":128,"playground":130,"body":132,"bodyMd":290,"seo":291,"translationGroupId":294,"series":52,"podcastUrl":52,"verticalId":5,"thread":295,"assessments":297,"translations":300,"quiz":302},"01a106cb-f870-70ac-892c-2f9a55db26b3","You're Animating for Everyone. `prefers-reduced-motion` Fixes That.","Most animated UIs never check whether the visitor asked for less motion. `prefers-reduced-motion` is the CSS media feature that tells you — and a fail-closed pattern keeps you honest even when a browser doesn't support it.","\u002Fmedia\u002Fcovers\u002Fprefers-reduced-motion-respect-the-setting.png",6,"2026-10-09T06:10:28.564Z",48,{"id":116,"name":117,"username":118,"avatarUrl":52,"headline":119},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":78,"name":122},"CSS",[124,125,126,127],{"slug":78,"name":79,"color":52},{"slug":105,"name":106,"color":52},{"slug":62,"name":63,"color":52},{"slug":70,"name":71,"color":52},{"assessments":129},1,{"slug":46,"title":131},"prefers-reduced-motion — interactive playground",{"blocks":133,"version":129},[134,138,141,145,148,153,156,159,163,166,169,172,176,179,183,186,190,193,196,200,203,206,211,214,217,220,223,226,229,232,235,238,241,244,247,250,253,256,259,262,265,272,275,278,281,284],{"id":135,"html":136,"type":137},"b1","\u003Cp>Somewhere in your traffic right now is a visitor who went into their OS settings and turned on &quot;Reduce motion.&quot; Maybe it&#39;s vestibular — a parallax hero genuinely makes them reach for the back button, sometimes mid-scroll, stomach first. Maybe it&#39;s just a preference. Either way, they told their computer. Your CSS never asked.\u003C\u002Fp>","paragraph",{"id":139,"html":140,"type":137},"b2","\u003Cp>That&#39;s the part that&#39;s easy to miss: this isn&#39;t a setting you&#39;d need to build. It already exists, it&#39;s already wired into every major OS, and the browser already knows the answer. There&#39;s a one-line CSS media feature that reads it. Almost no one uses it, and most of the people who do use it get the &quot;safe&quot; version backwards — in a way that doesn&#39;t look wrong in a code review.\u003C\u002Fp>",{"id":142,"html":143,"text":143,"type":144,"level":31},"b3","The fix that breaks the loading spinner","heading",{"id":146,"html":147,"type":137},"b4","\u003Cp>Here&#39;s what usually happens the first time someone audits a site for motion sensitivity and feels bad about what they find. They add one rule at the top of the stylesheet and call it done:\u003C\u002Fp>",{"id":149,"code":150,"type":151,"language":78,"highlight":152},"b5","@media (prefers-reduced-motion: reduce) {\n  *, *::before, *::after {\n    animation: none !important;\n    transition: none !important;\n  }\n}","code",[],{"id":154,"html":155,"type":137},"b6","\u003Cp>Ship it, close the ticket, feel good. Then the bug reports start: the loading spinner doesn&#39;t spin anymore — it just sits there, frozen, looking broken instead of busy. The focus ring that used to ease in now snaps so hard it looks like a rendering glitch. The toast that confirms &quot;Saved&quot; pops in and out with no transition at all, so on a slow glance it looks like nothing happened.\u003C\u002Fp>",{"id":157,"html":158,"type":137},"b7","\u003Cp>Turns out \u003Ccode>!important\u003C\u002Fcode> doesn&#39;t know the difference between a parallax hero that exists purely to look cool and a spinner whose entire job is to show the page is still alive. It nuked both.\u003C\u002Fp>",{"id":160,"html":161,"text":162,"type":144,"level":31},"b8","Decorative motion and motion that&#39;s doing a job","Decorative motion and motion that's doing a job",{"id":164,"html":165,"type":137},"b9","\u003Cp>Here&#39;s the distinction the blanket rule missed, and it&#39;s the one thing to take from this whole post if you take nothing else: \u003Cstrong>&quot;reduce motion&quot; does not mean &quot;delete all motion.&quot;\u003C\u002Fstrong>\u003C\u002Fp>",{"id":167,"html":168,"type":137},"b10","\u003Cp>The accessibility guidance behind this setting (it&#39;s closely related to \u003Ca href=\"https:\u002F\u002Fwww.w3.org\u002FWAI\u002FWCAG21\u002FUnderstanding\u002Fanimation-from-interactions.html\">WCAG&#39;s Animation from Interactions criterion\u003C\u002Fa>, though that one specifically covers interaction-triggered motion) is about \u003Cem>non-essential\u003C\u002Fem> motion — parallax, auto-playing background video, swooping page transitions, anything whose only purpose is visual flair. Motion that&#39;s carrying information — a spinner proving a request is in flight, a focus ring proving where keyboard focus landed, a progress bar — should usually still move, just without the large transforms (scale, translate, 3D) that trigger a vestibular reaction. A spinner that still spins in place is fine. A spinner that slides across the screen while it spins is not.\u003C\u002Fp>",{"id":170,"html":171,"type":137},"b11","\u003Cp>So the real pattern isn&#39;t &quot;delete everything inside the reduce query.&quot; It&#39;s &quot;write the big, decorative motion so it only exists when the browser says no one asked for less of it&quot;:\u003C\u002Fp>",{"id":173,"code":174,"type":151,"language":78,"highlight":175},"b12",".hero-card {\n  opacity: 1;\n  transform: none;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n  .hero-card {\n    \u002F* the swoop-in effect lives entirely inside this query *\u002F\n    animation: swoop-in 600ms ease-out;\n  }\n}\n\n.spinner {\n  \u002F* rotation only — no translate, no scale — so it's left alone *\u002F\n  animation: spin 900ms linear infinite;\n}",[],{"id":177,"html":178,"type":137},"b13","\u003Cp>Notice which media feature that first block uses: \u003Ccode>no-preference\u003C\u002Fcode>, not \u003Ccode>reduce\u003C\u002Fcode>. That&#39;s not a stylistic choice — it&#39;s the detail that makes the whole thing fail closed.\u003C\u002Fp>",{"id":180,"html":181,"text":182,"type":144,"level":31},"b14","Why \u003Ccode>no-preference\u003C\u002Fcode> is the one to reach for","Why no-preference is the one to reach for",{"id":184,"html":185,"type":137},"b15","\u003Cp>Quick check before you scroll: which of these two is safer if a browser, for whatever reason, doesn&#39;t evaluate the media feature at all?\u003C\u002Fp>",{"id":187,"code":188,"type":151,"language":78,"highlight":189},"b16","\u002F* A: opt OUT of motion *\u002F\n.hero-card { animation: swoop-in 600ms ease-out; }\n@media (prefers-reduced-motion: reduce) {\n  .hero-card { animation: none; }\n}\n\n\u002F* B: opt IN to motion *\u002F\n.hero-card { animation: none; }\n@media (prefers-reduced-motion: no-preference) {\n  .hero-card { animation: swoop-in 600ms ease-out; }\n}",[],{"id":191,"html":192,"type":137},"b17","\u003Cp>In version A, the animation is the default — it runs unless the \u003Ccode>reduce\u003C\u002Fcode> query actively matches. If the media feature can&#39;t be evaluated for any reason, nothing turns it off, and the visitor who asked for less motion gets the full swoop anyway. In version B, the animation is the thing that has to be earned — it only runs when the browser can positively confirm \u003Ccode>no-preference\u003C\u002Fcode>. If evaluation fails for any reason, the safe, motionless default is what&#39;s left standing.\u003C\u002Fp>",{"id":194,"html":195,"type":137},"b18","\u003Cp>\u003Ccode>prefers-reduced-motion\u003C\u002Fcode> itself has excellent support in evergreen browsers today, so this isn&#39;t about Internet Explorer. It&#39;s about not betting your accessibility behavior on an active override firing correctly, when you could just as easily make stillness the thing that happens by default and motion the thing you have to opt into.\u003C\u002Fp>",{"id":197,"html":198,"text":199,"type":144,"level":31},"b19","The part CSS can&#39;t fix for you","The part CSS can't fix for you",{"id":201,"html":202,"type":137},"b20","\u003Cp>Here&#39;s the open loop from the start of this post: the visitor whose stomach can&#39;t handle your parallax hero isn&#39;t only fighting your CSS. A lot of the motion that actually triggers a reaction never goes through a \u003Ccode>transition\u003C\u002Fcode> or \u003Ccode>animation\u003C\u002Fcode> property at all — it&#39;s a \u003Ccode>requestAnimationFrame\u003C\u002Fcode> loop moving a background layer on scroll, or a hero video with \u003Ccode>autoplay\u003C\u002Fcode> and no regard for anyone&#39;s settings. \u003Ccode>@media (prefers-reduced-motion: reduce)\u003C\u002Fcode> has zero effect on either, because neither one is CSS.\u003C\u002Fp>",{"id":204,"html":205,"type":137},"b21","\u003Cp>JavaScript gets the same preference through \u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FWindow\u002FmatchMedia\">\u003Ccode>window.matchMedia()\u003C\u002Fcode>\u003C\u002Fa> — the same API behind \u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fmatchmedia-watch-media-queries-from-js\">watching other media queries from JS\u003C\u002Fa> — and, same as any other media query, you can subscribe to it changing live, without a page reload:\u003C\u002Fp>",{"id":207,"code":208,"type":151,"language":209,"highlight":210},"b22","const reduceMotion = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n\nfunction applyPreference(matches) {\n  if (matches) {\n    cancelAnimationFrame(parallaxLoop);\n    heroVideo.pause();\n    heroVideo.removeAttribute(\"autoplay\");\n  } else {\n    parallaxLoop = requestAnimationFrame(runParallax);\n  }\n}\n\napplyPreference(reduceMotion.matches);\nreduceMotion.addEventListener(\"change\", (e) => applyPreference(e.matches));","js",[],{"id":212,"html":213,"type":137},"b23","\u003Cp>That \u003Ccode>change\u003C\u002Fcode> listener matters more than it looks: if someone flips &quot;Reduce motion&quot; on in their OS settings while your tab is already open — which is exactly what someone mid-vertigo is likely to do — the parallax loop stops without them needing to reload anything.\u003C\u002Fp>",{"id":215,"html":216,"type":137},"b24","\u003C!-- playground:start -->",{"id":218,"html":219,"text":219,"type":144,"level":31},"b25","🎮 Try it yourself",{"id":221,"html":222,"type":137},"b26","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fprefers-reduced-motion-respect-the-setting\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":224,"html":225,"type":137},"b27","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":227,"html":228,"type":137},"b28","\u003C!-- playground:end -->",{"id":230,"html":231,"text":231,"type":144,"level":31},"b29","The honest caveat",{"id":233,"html":234,"type":137},"b30","\u003Cp>One thing this setting can&#39;t do: tell you about a visitor who has the exact same sensitivity but never found, or never bothered with, the OS toggle. \u003Ccode>prefers-reduced-motion: no-preference\u003C\u002Fcode> is what you get both from someone who genuinely loves motion \u003Cem>and\u003C\u002Fem> from someone who simply doesn&#39;t know the setting exists. Respecting the media query is necessary, not sufficient — if your product leans heavily on motion, an in-app &quot;reduce animations&quot; toggle that doesn&#39;t require touching OS settings is still worth having alongside it, not instead of it.\u003C\u002Fp>",{"id":236,"html":237,"text":237,"type":144,"level":31},"b31","The takeaway",{"id":239,"html":240,"type":137},"b32","\u003Cp>The setting was never the hard part — it&#39;s one media feature, supported everywhere that matters, and your OS has been asking the browser about it for years. The hard part is remembering that &quot;respect it&quot; means \u003Cem>separate decorative motion from functional motion\u003C\u002Fem> and \u003Cem>default to still\u003C\u002Fem>, not \u003Cem>delete transitions wherever you find the word \u003Ccode>animation\u003C\u002Fcode>\u003C\u002Fem>. Next time you reach for that one global override, flip it around: wrap the swoop in \u003Ccode>no-preference\u003C\u002Fcode> instead of silencing it in \u003Ccode>reduce\u003C\u002Fcode>.\u003C\u002Fp>",{"id":242,"html":243,"type":137},"b33","\u003Cp>Go check your hero section right now — does the parallax or the autoplaying background video even know this setting exists, or does it just keep moving no matter what the visitor asked for?\u003C\u002Fp>",{"id":245,"html":246,"type":137},"b34","\u003C!-- quiz:start -->",{"id":248,"html":249,"text":249,"type":144,"level":31},"b35","🧠 Test yourself",{"id":251,"html":252,"type":137},"b36","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fprefers-reduced-motion-respect-the-setting\u002Fquiz\">Take the 8-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":254,"html":255,"type":137},"b37","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":257,"html":258,"type":137},"b38","\u003C!-- quiz:end -->",{"id":260,"html":261,"type":137},"b39","\u003C!-- related:start -->",{"id":263,"html":264,"text":264,"type":144,"level":31},"b40","📚 Read next",{"id":266,"type":267,"items":268,"ordered":18},"b41","list",[269,270,271],"\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-color-scheme-native-dark-mode-form-controls\">The CSS Property That Fixes Dark Mode&#39;s Ugly White Boxes\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-accent-color-native-form-control-tinting\">One CSS Property Replaces Your Checkbox Hack\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-clamp-fluid-typography\">Stop Writing Media Queries for Font Size\u003C\u002Fa>",{"id":273,"html":274,"type":137},"b42","\u003C!-- related:end -->",{"id":276,"type":277},"b43","divider",{"id":279,"html":280,"type":137},"b44","\u003Cp>🚀 \u003Cstrong>Want more like this?\u003C\u002Fstrong> Every guide, playground, and quiz lives on \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">bestpractic.org\u003C\u002Fa>\u003C\u002Fstrong> — open it and \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">sign up free\u003C\u002Fa>\u003C\u002Fstrong> so the next one finds you.\u003C\u002Fp>",{"id":282,"html":283,"type":137},"b45","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":285,"type":267,"items":286,"ordered":18},"b46",[287,288,289],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","Somewhere in your traffic right now is a visitor who went into their OS settings and turned on \"Reduce motion.\" Maybe it's vestibular — a parallax hero genuinely makes them reach for the back button, sometimes mid-scroll, stomach first. Maybe it's just a preference. Either way, they told their computer. Your CSS never asked.\n\nThat's the part that's easy to miss: this isn't a setting you'd need to build. It already exists, it's already wired into every major OS, and the browser already knows the answer. There's a one-line CSS media feature that reads it. Almost no one uses it, and most of the people who do use it get the \"safe\" version backwards — in a way that doesn't look wrong in a code review.\n\n## The fix that breaks the loading spinner\n\nHere's what usually happens the first time someone audits a site for motion sensitivity and feels bad about what they find. They add one rule at the top of the stylesheet and call it done:\n\n```css\n@media (prefers-reduced-motion: reduce) {\n  *, *::before, *::after {\n    animation: none !important;\n    transition: none !important;\n  }\n}\n```\n\nShip it, close the ticket, feel good. Then the bug reports start: the loading spinner doesn't spin anymore — it just sits there, frozen, looking broken instead of busy. The focus ring that used to ease in now snaps so hard it looks like a rendering glitch. The toast that confirms \"Saved\" pops in and out with no transition at all, so on a slow glance it looks like nothing happened.\n\nTurns out `!important` doesn't know the difference between a parallax hero that exists purely to look cool and a spinner whose entire job is to show the page is still alive. It nuked both.\n\n## Decorative motion and motion that's doing a job\n\nHere's the distinction the blanket rule missed, and it's the one thing to take from this whole post if you take nothing else: **\"reduce motion\" does not mean \"delete all motion.\"**\n\nThe accessibility guidance behind this setting (it's closely related to [WCAG's Animation from Interactions criterion](https:\u002F\u002Fwww.w3.org\u002FWAI\u002FWCAG21\u002FUnderstanding\u002Fanimation-from-interactions.html), though that one specifically covers interaction-triggered motion) is about *non-essential* motion — parallax, auto-playing background video, swooping page transitions, anything whose only purpose is visual flair. Motion that's carrying information — a spinner proving a request is in flight, a focus ring proving where keyboard focus landed, a progress bar — should usually still move, just without the large transforms (scale, translate, 3D) that trigger a vestibular reaction. A spinner that still spins in place is fine. A spinner that slides across the screen while it spins is not.\n\nSo the real pattern isn't \"delete everything inside the reduce query.\" It's \"write the big, decorative motion so it only exists when the browser says no one asked for less of it\":\n\n```css\n.hero-card {\n  opacity: 1;\n  transform: none;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n  .hero-card {\n    \u002F* the swoop-in effect lives entirely inside this query *\u002F\n    animation: swoop-in 600ms ease-out;\n  }\n}\n\n.spinner {\n  \u002F* rotation only — no translate, no scale — so it's left alone *\u002F\n  animation: spin 900ms linear infinite;\n}\n```\n\nNotice which media feature that first block uses: `no-preference`, not `reduce`. That's not a stylistic choice — it's the detail that makes the whole thing fail closed.\n\n## Why `no-preference` is the one to reach for\n\nQuick check before you scroll: which of these two is safer if a browser, for whatever reason, doesn't evaluate the media feature at all?\n\n```css\n\u002F* A: opt OUT of motion *\u002F\n.hero-card { animation: swoop-in 600ms ease-out; }\n@media (prefers-reduced-motion: reduce) {\n  .hero-card { animation: none; }\n}\n\n\u002F* B: opt IN to motion *\u002F\n.hero-card { animation: none; }\n@media (prefers-reduced-motion: no-preference) {\n  .hero-card { animation: swoop-in 600ms ease-out; }\n}\n```\n\nIn version A, the animation is the default — it runs unless the `reduce` query actively matches. If the media feature can't be evaluated for any reason, nothing turns it off, and the visitor who asked for less motion gets the full swoop anyway. In version B, the animation is the thing that has to be earned — it only runs when the browser can positively confirm `no-preference`. If evaluation fails for any reason, the safe, motionless default is what's left standing.\n\n`prefers-reduced-motion` itself has excellent support in evergreen browsers today, so this isn't about Internet Explorer. It's about not betting your accessibility behavior on an active override firing correctly, when you could just as easily make stillness the thing that happens by default and motion the thing you have to opt into.\n\n## The part CSS can't fix for you\n\nHere's the open loop from the start of this post: the visitor whose stomach can't handle your parallax hero isn't only fighting your CSS. A lot of the motion that actually triggers a reaction never goes through a `transition` or `animation` property at all — it's a `requestAnimationFrame` loop moving a background layer on scroll, or a hero video with `autoplay` and no regard for anyone's settings. `@media (prefers-reduced-motion: reduce)` has zero effect on either, because neither one is CSS.\n\nJavaScript gets the same preference through [`window.matchMedia()`](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FWindow\u002FmatchMedia) — the same API behind [watching other media queries from JS](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fmatchmedia-watch-media-queries-from-js) — and, same as any other media query, you can subscribe to it changing live, without a page reload:\n\n```js\nconst reduceMotion = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n\nfunction applyPreference(matches) {\n  if (matches) {\n    cancelAnimationFrame(parallaxLoop);\n    heroVideo.pause();\n    heroVideo.removeAttribute(\"autoplay\");\n  } else {\n    parallaxLoop = requestAnimationFrame(runParallax);\n  }\n}\n\napplyPreference(reduceMotion.matches);\nreduceMotion.addEventListener(\"change\", (e) => applyPreference(e.matches));\n```\n\nThat `change` listener matters more than it looks: if someone flips \"Reduce motion\" on in their OS settings while your tab is already open — which is exactly what someone mid-vertigo is likely to do — the parallax loop stops without them needing to reload anything.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fprefers-reduced-motion-respect-the-setting\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## The honest caveat\n\nOne thing this setting can't do: tell you about a visitor who has the exact same sensitivity but never found, or never bothered with, the OS toggle. `prefers-reduced-motion: no-preference` is what you get both from someone who genuinely loves motion *and* from someone who simply doesn't know the setting exists. Respecting the media query is necessary, not sufficient — if your product leans heavily on motion, an in-app \"reduce animations\" toggle that doesn't require touching OS settings is still worth having alongside it, not instead of it.\n\n## The takeaway\n\nThe setting was never the hard part — it's one media feature, supported everywhere that matters, and your OS has been asking the browser about it for years. The hard part is remembering that \"respect it\" means *separate decorative motion from functional motion* and *default to still*, not *delete transitions wherever you find the word `animation`*. Next time you reach for that one global override, flip it around: wrap the swoop in `no-preference` instead of silencing it in `reduce`.\n\nGo check your hero section right now — does the parallax or the autoplaying background video even know this setting exists, or does it just keep moving no matter what the visitor asked for?\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 8-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fprefers-reduced-motion-respect-the-setting\u002Fquiz)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\n\u003C!-- related:start -->\n\n## 📚 Read next\n\n- [The CSS Property That Fixes Dark Mode's Ugly White Boxes](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-color-scheme-native-dark-mode-form-controls)\n- [One CSS Property Replaces Your Checkbox Hack](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-accent-color-native-form-control-tinting)\n- [Stop Writing Media Queries for Font Size](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-clamp-fluid-typography)\n\n\u003C!-- related:end -->\n\n---\n\n🚀 **Want more like this?** Every guide, playground, and quiz lives on **[bestpractic.org](https:\u002F\u002Fbestpractic.org\u002F)** — open it and **[sign up free](https:\u002F\u002Fbestpractic.org\u002F)** so the next one finds you.\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)",{"title":109,"canonical":292,"description":293},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fprefers-reduced-motion-respect-the-setting","Most animated UIs never check whether the visitor asked for less motion. `prefers-reduced-motion` is the CSS media feature that tells you — and a fail-closed pattern keeps you hone","01a106cb-f870-70ac-892c-32692052e0b6",{"id":296,"locked":18},"01a106cb-f899-7764-bdfa-948215bd5644",[298],{"id":45,"slug":46,"title":48,"_count":299},{"questions":51},[301],{"locale":13,"slug":46},{"id":45,"slug":46,"title":48,"_count":303,"questionCount":51},{"questions":51}]