CSS Scroll Snap: How to Create Smooth Scrolling Sections
Build section-based scrolling with CSS Scroll Snap. Learn how to choose snap behavior, handle long sections and fixed headers, and troubleshoot common issues.
Use CSS Scroll Snap to make a scroll container settle on chosen positions as a visitor scrolls. Set scroll-snap-type on the container, give each target a scroll-snap-align, and make sure the container has a defined size and scrollable overflow. For a starting point, use proximity: it allows snapping without forcing every scroll to a target. Choose mandatory only when every target and its content remain easy to reach.
Scroll snapping controls where scrolling settles; it does not guarantee a particular animation curve or identical smoothness across browsers. The browser controls the motion. The CSS Scroll Snap specification explicitly leaves animation and physics to the user agent.
1. Make a vertical section scroller
This runnable example creates a viewport-sized scrolling region with three snap targets. Save it as an HTML file and open it in a browser.
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Scroll snap sections</title>
<style>
* { box-sizing: border-box; }
html, body { margin: 0; }
.story-scroller {
block-size: 100vh;
block-size: 100dvh;
overflow-y: auto;
scroll-snap-type: y proximity;
}
.panel {
min-block-size: 100%;
padding: 3rem 1.5rem;
display: grid;
align-content: center;
scroll-snap-align: start;
}
.panel:nth-child(odd) { background: #eaf2ff; }
.panel:nth-child(even) { background: #fff3e8; }
</style>
<main class="story-scroller">
<section class="panel"><h1>First section</h1><p>Scroll to continue.</p></section>
<section class="panel"><h2>Second section</h2><p>This section can grow with its content.</p></section>
<section class="panel"><h2>Third section</h2><p>The end of the story.</p></section>
</main>
</html>
The scroller is the element with both a bounded block size and overflow-y: auto. Its children are the snap targets. min-block-size: 100% gives short panels at least one scroller’s height while allowing taller content to expand. It does not force every panel to be exactly viewport height.
2. Choose strictness and alignment
| Setting | Effect | Use it when |
|---|---|---|
scroll-snap-type: y proximity |
The browser may snap when scrolling ends near a target. | You want section cues while keeping free scrolling easy. |
scroll-snap-type: y mandatory |
The browser enforces a defined snap position after scrolling. | Every target is reachable and the content layout has been checked for long or variable-height sections. |
scroll-snap-align: start |
Aligns the target’s start edge with the snapport. | Reading sections from their beginning is the intended pattern. |
scroll-snap-align: center |
Aligns the target’s center. | Cards or discrete items should settle centrally. |
scroll-snap-align: end |
Aligns the target’s end edge. | The end edge is the useful reference point. |
For horizontal carousels, use scroll-snap-type: x proximity (or mandatory when justified) and typically scroll-snap-align: start on each item. For both axes, use both, but test diagonal and touch scrolling so the behavior remains predictable. The axis can also be expressed with logical directions such as block and inline.
With mandatory, make sure all content can be reached. A tall target may contain useful points that are not themselves snap targets; users can find some portions difficult to reach, particularly if the browser keeps snapping away from them. Ensure targets are aligned and avoid mandatory snapping for layouts where content height varies substantially unless testing confirms the entire content remains navigable. MDN also cautions that an unaligned child inside a mandatory scroller may not be reachable as expected. See MDN’s scroll snap concepts.
3. Account for fixed headers and target spacing
The container’s scroll-padding adjusts its snapport, which is useful when a fixed header covers the top of the page. A target’s scroll-margin adjusts that target’s snap area.
.story-scroller {
scroll-snap-type: y proximity;
scroll-padding-block-start: 4rem; /* reserve space for a fixed header */
}
.panel {
scroll-snap-align: start;
scroll-margin-block-start: 0.5rem;
}
Set the padding to match the actual obstruction at each responsive breakpoint. If the header changes height, update the value with a media query or a CSS custom property. Avoid adding the same offset in several places without checking the final snap position; container padding and target margin affect different parts of the snap geometry.
4. Use scrolling without making motion a requirement
Users may scroll with a touch gesture, wheel, keyboard, or browser controls. Keep normal document semantics and focusable content; scroll snap should enhance navigation rather than replace it. Test the actual page with keyboard navigation and touch input, including when a section is taller than the visible area. Do not assume the snap property itself provides a reduced-motion experience or an accessibility guarantee.
CSS Scroll Snap does not promise a fixed duration, easing curve, or physical feel. If you also use scripted scrolling, the separate scroll-behavior property can affect programmatic scrolling, but it does not define the snap animation. Avoid adding JavaScript that intercepts every wheel or touch event just to simulate snapping; native scrolling retains browser input behavior.
5. Make sections responsive and content-safe
- Prefer
min-block-sizeover a rigid height when text, translations, or embedded content can grow. - Check short viewports, browser zoom, larger text settings, and narrow screens. A panel that fits on a desktop may become much taller on a phone.
- Keep headings, form controls, and links in normal document flow so content remains available when snapping is skipped or unsupported.
- For nested scrollers, test which element receives wheel and touch input. Nested overflow regions can make it unclear which container is moving.
- Use a scroll container only when the design needs one. For ordinary page sections, applying snap behavior to the document may be simpler than creating a separately scrolling panel.
6. Browser support and verification
MDN marks scroll-snap-type as Baseline and reports broad availability since April 2022. If you support specific browser versions, check current compatibility data for those targets in MDN’s property reference. The W3C module index lists Scroll Snap Level 1 and the Level 2 draft; for these core patterns, the Level 1 model describes the relevant container, snapport, and target relationships.
Verify behavior in your support matrix rather than relying on a screenshot or a single browser. A screenshot can confirm the rendered appearance at one scroll position, but it cannot establish whether touch, keyboard, or wheel scrolling can reach every part of the content.
7. Troubleshoot common problems
| Symptom | Likely cause | Fix |
|---|---|---|
| Nothing snaps. | The element is not actually scrollable, no size constraint creates overflow, the wrong axis is set, or targets lack alignment. | Give the intended container a bounded size and overflow-y: auto; set a matching axis and add scroll-snap-align to targets. |
| The whole page moves instead of the section panel. | The intended scroller has no constrained block size, or the user is scrolling outside it. | Check computed size and overflow in developer tools; make sure the scroll gesture starts over the scroller. |
| A fixed header covers the section heading. | The snapport starts behind the header. | Set responsive scroll-padding-block-start on the scroll container to account for the header. |
| Some text in a long section is hard to reach. | mandatory snapping and sparse snap targets can make intermediate content difficult to access. |
Use proximity, add sensible targets, or redesign the long section; retest the full content at small viewport sizes. |
| Snapping feels too aggressive. | mandatory is too strict for the content or input pattern. |
Try proximity. Snap strictness is a design choice, not a smoothness setting. |
| Panels have unexpected gaps or offsets. | Margins, scroll padding, scroll margin, or box sizing change the snap area and snapport. | Inspect computed styles and simplify offsets; remember that container padding and target margin have different roles. |
| The example works on desktop but clips on mobile. | Fixed viewport heights or content assumptions do not hold on a short viewport. | Use a minimum height, allow panels to grow, and test with mobile browser chrome, zoom, and larger text. |
8. Performance, reliability, and cost
CSS Scroll Snap uses browser scrolling and layout; this pattern requires no third-party service or API request. Its practical cost is the time spent checking real content, input methods, and supported browsers. Avoid unnecessary JavaScript scroll handlers that repeatedly measure layout or override native input. If a section contains expensive media or embeds, address that resource’s loading and rendering separately; scroll snap does not make those resources faster.
For documentation, review, or visual regression workflows, ScreenshotNeo can capture a page as PNG, JPEG, WebP, or PDF through one API request. Its capture options include full-page screenshots with lazy images loaded and custom wait conditions. A screenshot is useful for checking appearance at a point in time; it does not replace interaction testing of snapping.
9. Or skip the browser setup
If you need a rendered screenshot of a page while documenting or reviewing this pattern, call the ScreenshotNeo API directly:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Scroll_snap/Basic_concepts -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Scroll_snap/Basic_concepts"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Scroll_snap/Basic_concepts' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server lets AI agents use screenshot tools. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Every feature is available on every plan.
Sign up free for 1,000 screenshots a month, with no card required.
10. FAQ
Does scroll snap make scrolling smooth?
It defines where scrolling can settle. The browser chooses the animation and physics, so it does not guarantee one smoothness curve.
Should I always use mandatory for full-screen sections?
No. Use it only if users can reach all content in every section. Proximity is a less restrictive starting point.
Can I use scroll snap for a carousel?
Yes. Set the horizontal axis on the scrolling container and an alignment on each item, then check touch and keyboard access.
Does a screenshot prove that the scroll interaction works?
No. A screenshot shows a rendered state. Verify scrolling and reachability in the browser with the input methods your audience uses.


