How to Create Smooth Scrolling with CSS
Make anchor links and programmatic navigation scroll smoothly with CSS, respect reduced-motion preferences, and troubleshoot common issues.
To make in-page links scroll smoothly, set scroll-behavior: smooth on the root element. For an independently scrollable region, set it on that region instead. Respect users who request less motion by switching to auto when prefers-reduced-motion: reduce is active.
html {
scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
}
This works with ordinary links such as <a href="#details">Details</a> when the page contains a matching id="details". The link remains a semantic, native link; CSS changes how the browser moves to its destination.
1. How CSS smooth scrolling works
The scroll-behavior property controls scrolling triggered by navigation or CSSOM scrolling APIs. auto scrolls instantly; smooth asks the browser to animate the movement. It does not affect direct wheel or touch scrolling, and browsers may ignore the property. See the [MDN property reference](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/scroll-behavior).
The browser chooses the easing and duration. CSS scroll-behavior does not provide a duration or timing-function setting. The [W3C CSSOM View specification](https://www.w3.org/TR/cssom-view/) describes smooth scrolling as taking place over a user-agent-defined amount of time, and notes that scrolling can be aborted by the user or by an algorithm.
2. Smooth scrolling for page anchors
Put the declaration on html, the document root. Then use normal fragment links and unique IDs:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Smooth anchor navigation</title>
<style>
html {
scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
}
</style>
</head>
<body>
<nav aria-label="On this page">
<a href="#overview">Overview</a>
<a href="#details">Details</a>
</nav>
<main>
<section id="overview">
<h2>Overview</h2>
<p>Page content goes here.</p>
</section>
<section id="details">
<h2>Details</h2>
<p>More page content goes here.</p>
</section>
</main>
</body>
</html>
Keep IDs unique and make sure each link’s fragment matches its target exactly. The root element is the right place for document viewport scrolling; setting this property on body does not propagate it to the viewport. MDN documents the root-element behavior and browser support in its [reference](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/scroll-behavior).
3. Smooth scrolling inside a nested container
If a panel has its own scroll bar, apply the property to that scrolling box. The panel needs a constrained size and scrollable overflow to act as a nested scroll container.
.scroll-container {
height: 24rem;
overflow-y: auto;
scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
.scroll-container {
scroll-behavior: auto;
}
}
Use links whose targets are descendants of that panel:
<nav aria-label="Panel sections">
<a href="#panel-intro">Introduction</a>
<a href="#panel-api">API</a>
</nav>
<div class="scroll-container">
<section id="panel-intro"><h2>Introduction</h2></section>
<section id="panel-api"><h2>API</h2></section>
</div>
Apply the rule to the element that actually scrolls. If an outer page and an inner panel both scroll, configure each independently according to the intended behavior.
4. Respect reduced-motion preferences
The prefers-reduced-motion media feature reflects a device setting for minimizing nonessential motion. Some animations can cause discomfort for people with vestibular motion disorders; for this scrolling effect, the straightforward accommodation is instant movement when the preference is set. See [MDN’s reduced-motion reference](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/%40media/prefers-reduced-motion).
Place the reduced-motion rule after the general smooth rule so it wins in the cascade. Use the same pattern for the root element or a nested scrolling container. The media query does not disable unrelated animations automatically; those need their own reduced-motion treatment.
5. What CSS can and cannot configure
| Setting | Effect | Important limit |
|---|---|---|
scroll-behavior: auto |
Scrolls instantly when navigation or an API initiates movement. | Does not mean wheel or touch input is disabled. |
scroll-behavior: smooth |
Requests smooth movement for navigation and CSSOM scrolling. | The browser chooses timing and easing; CSS offers no fixed duration here. |
Declaration on html |
Controls document viewport scrolling. | A declaration on body does not propagate to the viewport. |
| Declaration on a scroll container | Controls scrolling in that element. | It must be the element whose overflow area actually scrolls. |
prefers-reduced-motion: reduce |
Can switch this movement to instant scrolling. | Other motion effects require their own accommodation. |
The property is not animatable. If a design requires a specific duration or easing curve, scroll-behavior alone cannot express it; this browser-controlled property intentionally leaves those details to the user agent.
6. Troubleshooting smooth scrolling
Anchor links jump instantly
- Check that the rule is loaded and applies to
htmlfor page-level navigation. - Check that the link fragment and target ID match, and that the ID is unique.
- Confirm a later or more specific CSS rule is not overriding
scroll-behavior. - Remember that browsers are allowed to ignore the property, and the reduced-motion query intentionally sets it to
auto.
A nested panel does not scroll smoothly
- Put the declaration on the element with
overflow-y: autooroverflow: scroll, not only on the page root. - Make sure the target element is inside that scroll container.
- Give the container a constrained height or max-height so it has an overflow area to scroll.
Wheel or touch scrolling is not animated
This is expected: the property does not change direct user scrolling. It applies when navigation or a scrolling API initiates movement.
The motion is still smooth with the preference enabled
Verify the operating-system or device reduced-motion setting, inspect whether the media query matches, and ensure the override comes after the base declaration with enough specificity to win.
I need a specific speed or easing
The CSS property does not expose duration or easing. The browser controls both, and user input or an algorithm can interrupt the movement. Avoid promising users identical timing across browsers.
7. Browser support, performance, and reliability
MDN marks scroll-behavior as Baseline Widely available and reports broad browser availability since March 2022. Browser compatibility and platform behavior can change, so check MDN’s live compatibility table if a specific minimum version is part of your support contract. Browsers may ignore the property, so the robust fallback is the native instant navigation that already works without it.
This CSS rule requires no JavaScript and adds no per-scroll script work. Keep anchor navigation semantic and ensure targets exist; native fragment navigation also remains useful when styling is unsupported. For reliability, test the page root and any nested scrollers separately, and check the reduced-motion state. No meaningful cost is associated with this CSS behavior itself.
8. Or skip the browser setup
If you need a screenshot of the page after adding or debugging scrolling behavior, ScreenshotNeo is a website screenshot API and MCP server. A single GET request returns a PNG, JPEG, WebP, or PDF. See the API documentation for the request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));
Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and billing status. An MCP server provides take_screenshot, get_page_info, and capture_pdf for AI agents. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
9. FAQ
Does smooth scrolling work on every link?
It affects scrolling triggered by navigation when the relevant scrolling box honors the property. Use a valid fragment link and matching target for in-page anchors.
Can I set the smooth-scroll duration in CSS?
No. The browser chooses the duration and easing for scroll-behavior: smooth.
Should I use html or body?
Use html for document viewport scrolling. Use the actual scroll container for an independently scrolling region.
Does this replace JavaScript scrolling?
For ordinary in-page navigation, CSS plus semantic anchor links is enough. The property also applies to scrolling initiated through CSSOM APIs, but it does not provide custom timing controls.


