A scroll animation should never override a user's explicit request for less motion
scroll-behavior: smooth turns jumps to anchor targets into animated scrolling, but for people with vestibular disorders that same animation can trigger real physical symptoms. The robust pattern activates smooth scrolling only inside @media (prefers-reduced-motion: no-preference), so the operating system's explicit choice always wins.
Table of Contents
- 1. What scroll-behavior: smooth actually does and where it applies
- 2. Why smooth scrolling is a real problem for motion sensitive users
- 3. Detecting prefers-reduced-motion and using it as a switch
- 4. The robust @media pattern: activating smooth only under no-preference
- 5. Anchor links and history navigation: where scroll-behavior technically applies
- 6. Wiring scrollIntoView() and scrollTo() correctly into the pattern
- 7. Exceptions: when a short animation is still reasonable
- 8. Testing: operating system setting, DevTools emulation and manual checks
- 9. Best practices for accessible scroll behavior
- 10. Summary
- 11. FAQ
1. What scroll-behavior: smooth actually does and where it applies
The CSS property scroll-behavior: smooth makes a jump to an anchor target, for example through <a href="#section">, run as an animated scroll over several hundred milliseconds instead of an abrupt jump. It is usually set on html, which makes it apply globally to every native scroll trigger within the document, including anchor navigation, keyboard scrolling with page up/down, and programmatic scrolling through certain JavaScript APIs.
At first glance the effect looks purely cosmetic, but it has a real impact on orientation: an animated scroll visually shows the user where they currently are on the page and how far the target lies, while an abrupt jump skips that spatial context entirely. For most users that is a pleasant, orientation friendly detail, but for one specific user group it is exactly the opposite.
2. Why smooth scrolling is a real problem for motion sensitive users
People with vestibular disorders or general motion sensitivity can develop real physical symptoms from large, fast scroll animations, ranging from dizziness to nausea, especially when the animation is large, fast and unexpectedly triggered. A single click on a table of contents link that scrolls through the entire page in one fast motion can be a genuine accessibility problem for this group, not just an aesthetic preference.
That is exactly why the operating system setting Reduce Motion exists, which users deliberately turn on to suppress exactly these animations system wide, and which browsers expose through the CSS media query prefers-reduced-motion: reduce. Enabling scroll-behavior: smooth globally without regard for that setting ignores a choice the user explicitly made and delivers precisely the experience the setting is meant to protect against.
3. Detecting prefers-reduced-motion and using it as a switch
The prefers-reduced-motion media query has two values: no-preference when the user has made no particular choice, and reduce when motion reduction is active at the operating system level. The order of the logic matters: instead of setting smooth as the default and removing it again under reduce, the more robust approach is to stay on the browser default auto by default and activate smooth only inside @media (prefers-reduced-motion: no-preference).
This difference is more than a matter of taste: browsers without support for the media query ignore the whole rule and stay on the safe auto behavior, while with the reversed order, a non supporting but still motion sensitive user could accidentally get smooth scrolling once some other browser fallback kicks in. Using no-preference as the activation condition is therefore the safer default stance.
/* Default: browser's native, instant scroll behavior */
html {
scroll-behavior: auto;
}
/* Only opt in to smooth scrolling when the user has not
requested reduced motion */
@media (prefers-reduced-motion: no-preference) {
html {
scroll-behavior: smooth;
}
}
4. The robust @media pattern: activating smooth only under no-preference
The robust base pattern combines the media query with a deliberately chosen default: scroll-behavior: auto is the safe starting point for html, because it matches the actual user intent as long as no explicit preference for animation exists. Only inside @media (prefers-reduced-motion: no-preference) is smooth layered on top, which makes the behavior automatically adapt to any change in the operating system setting, with no JavaScript and no page reload required.
For projects that also want to offer a manual in-app toggle for animations, the same pattern can be paired with a data attribute that JavaScript flips once the user changes the app setting. The media query stays the primary, automatic source of truth, while the manual toggle serves only as an additional, deliberate override for users who want a different preference than their operating system.
html {
scroll-behavior: auto;
}
@media (prefers-reduced-motion: no-preference) {
/* data-motion="reduced" lets an in-app toggle override
the OS preference without touching JavaScript logic elsewhere */
html:not([data-motion="reduced"]) {
scroll-behavior: smooth;
}
}
html[data-motion="reduced"] {
scroll-behavior: auto;
}
5. Anchor links and history navigation: where scroll-behavior technically applies
scroll-behavior: smooth technically applies to every scroll triggered as the result of same-document navigation: clicking an anchor link with a fragment identifier, forward and back navigation in browser history whenever the target fragment changes, and keyboard navigation with page up, page down, home and end, as long as the focused context is the document itself. The property therefore covers not just clickable links but the document's entire native scroll behavior.
What it does not cover is any scrolling triggered purely by mouse wheel, trackpad gesture or touch swipe, because that kind of scrolling is already continuous and user driven and needs no discrete jump animation. Anyone worried that scroll-behavior: smooth would artificially slow down normal scrolling can rest easy: the property only affects jumps to a specific target triggered by navigation or code.
<nav>
<a href="#introduction">Introduction</a>
<a href="#main-section">Main Section</a>
<a href="#conclusion">Conclusion</a>
</nav>
<section id="introduction">...</section>
<section id="main-section">...</section>
<section id="conclusion">...</section>
<style>
/* These anchor jumps automatically become smooth once
scroll-behavior: smooth is active on <html> */
html {
scroll-behavior: auto;
}
@media (prefers-reduced-motion: no-preference) {
html {
scroll-behavior: smooth;
}
}
</style>
6. Wiring scrollIntoView() and scrollTo() correctly into the pattern
Programmatic scrolling from JavaScript through element.scrollIntoView() or window.scrollTo() only respects scroll-behavior: smooth on html automatically when no own behavior argument is passed, or when behavior: 'auto' is set explicitly. If the JavaScript call instead hardcodes behavior: 'smooth', that call overrides any CSS setting and, with it, ignores prefers-reduced-motion entirely, defeating the whole CSS based safeguard.
The clean solution is to decide motion behavior in exactly one place, either purely through CSS and JavaScript calls without an explicit behavior, or, if JavaScript has to make the decision, to query the same media query there too via window.matchMedia('(prefers-reduced-motion: reduce)') and translate the result consistently into the behavior value, instead of maintaining two parallel, contradictory sources of truth.
const prefersReducedMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)"
).matches;
document
.querySelector("#back-to-top")
.addEventListener("click", () => {
window.scrollTo({
top: 0,
behavior: prefersReducedMotion ? "auto" : "smooth",
});
});
7. Exceptions: when a short animation is still reasonable
Not every animation has to disappear completely once prefers-reduced-motion: reduce is active. The specification and common practice distinguish between large, spatially extensive movements, such as scrolling through an entire long page, and small, short transitions, such as a tooltip gently fading in and out over a few pixels and milliseconds, which usually remain unproblematic even for motion sensitive users.
For scroll interactions a more nuanced view is therefore worthwhile: a jump to a section far away on the page should genuinely happen instantly under reduced motion, while a very short, few pixel corrective scroll, for example nudging a form field just into the visible viewport, is in many cases still acceptable even with reduce active. When in doubt, though, the rule is to err on the side of caution rather than carelessness with the user's preference.
8. Testing: operating system setting, DevTools emulation and manual checks
The Reduce Motion setting can be found on Windows under Accessibility settings, on macOS under Accessibility settings, Display, and on most mobile operating systems in their respective accessibility menus as well. Once enabled, every modern browser evaluates the CSS media query correctly without requiring a page reload, because the computed value of the media query updates live alongside the system setting.
For development, Chromium and Firefox DevTools offer an emulation of prefers-reduced-motion directly in the rendering panel, letting you test the behavior without permanently changing your own system setting. It is still worth a short manual test with the system setting genuinely enabled, because individual browser engines do not always handle every edge case of the emulation exactly like the real operating system setting.
/* Quick visual debug helper: highlights every element whose
animation/transition is currently suppressed by the OS setting */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
scroll-behavior: auto !important;
}
}
9. Best practices for accessible scroll behavior
Accessible scroll behavior ultimately needs no complicated logic, just a consistent order: auto by default, smooth only inside @media (prefers-reduced-motion: no-preference), and every JavaScript call that sets its own behavior wired into the same preference check. That keeps a single, consistent source of truth for the page's entire scroll behavior.
Once this pattern is established globally on html, or in a central JavaScript utility, there is no need to rethink it for every new component, as long as new scroll calls consistently avoid a hardcoded behavior: 'smooth'. That turns accessible scrolling into a one-time architectural decision instead of a recurring source of bugs with every new feature.
| Trigger | Covered by scroll-behavior | Respects prefers-reduced-motion | Recommendation |
|---|---|---|---|
| Anchor link click (#target) | Yes | Yes, automatically via CSS | html { scroll-behavior: smooth } inside the media query |
| Browser back/forward with fragment | Yes | Yes, automatically via CSS | No extra code needed |
| Mouse wheel, trackpad, touch swipe | No | Not relevant | Always user driven, no animation |
| scrollIntoView() without behavior | Yes, inherits CSS value | Yes, automatically via CSS | Do not pass an explicit behavior |
| Hardcoded scrollTo({behavior:'smooth'}) | No, overrides CSS | No, must be checked manually | Query matchMedia yourself in JS |
Mironsoft
Modern CSS, layout architecture and rendering performance
CSS that stays maintainable instead of breaking with every change?
We review existing stylesheets for specificity chaos and layout thrashing, then build a CSS architecture with cascade layers, custom properties and modern layout primitives that still makes sense after the tenth feature.
CSS Audit
Systematically uncovering specificity issues, cascade conflicts and unused selectors.
Architecture Refactoring
Introducing cascade layers, custom properties and design tokens cleanly.
Performance Tuning
Fixing layout thrashing, expensive selectors and rendering bottlenecks.
10. Summary
scroll-behavior: smooth and prefers-reduced-motion: The Essentials at a Glance
Core idea
scroll-behavior: smooth animates jumps to anchors and programmatic scroll targets, but does not affect mouse wheel or touch scrolling.
Safe pattern
Default to auto, activate smooth only inside @media (prefers-reduced-motion: no-preference), never the other way around.
JavaScript trap
A hardcoded behavior: 'smooth' in scrollIntoView or scrollTo overrides CSS and must check the preference itself via matchMedia.
Testing
DevTools emulation for fast iteration, but finish with the real system setting on Windows, macOS and mobile devices.