CSS Scroll Snap Explained
CSS scroll snap locks scrolling to fixed positions using scroll-snap-type and scroll-snap-align, no JavaScript required. Here's how it works.
CSS scroll snap is a set of properties that make a scrollable container stop at defined positions instead of coming to rest wherever momentum happens to leave it — the carousel, full-page-slide, or image-gallery effect you’ve seen on countless sites, built entirely in CSS with no scroll-event listeners involved.
Before scroll snap shipped, this behavior meant JavaScript: listening for scroll or scrollend, calculating the nearest target position, and animating to it manually. That approach is janky on lower-powered devices because it fights the browser’s native scroll physics. Scroll snap works with the browser’s scroll handling instead of against it, so the snapping feels as smooth as the platform’s native momentum scrolling.
The two core properties
Scroll snap needs two things: a container that declares it snaps, and children that declare where they snap to.
.gallery {
scroll-snap-type: x mandatory;
overflow-x: auto;
display: flex;
}
.gallery > * {
scroll-snap-align: start;
}
scroll-snap-typegoes on the scroll container. The first value is the axis (x,y, orboth); the second is strictness.mandatorymeans the browser always settles on a snap point after a scroll gesture ends.proximityonly snaps if the scroll stopped near a snap point — if the user stops mid-scroll, it leaves the position alone.scroll-snap-aligngoes on each child, declaring which edge of that child aligns with the container:start,end, orcenter.
A horizontal gallery, start to finish
.gallery {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
gap: 1rem;
}
.gallery > .card {
flex: 0 0 80%;
scroll-snap-align: center;
}
Each .card takes up 80% of the container width and snaps to center as the user swipes or drags. No JavaScript, no scroll listeners, no manual scrollTo() calculations — and because it’s handled by the browser’s own compositor, it stays smooth even while other work is happening on the main thread, unlike a hand-rolled scroll handler.
scroll-snap-stop
By default, a fast swipe can skip past several snap points to land wherever momentum carries it — normal for a photo carousel where users expect to flick through several images at once. scroll-snap-stop: always forces the browser to stop at every snap point instead, which matters for full-page sections where skipping past a section would be disorienting:
.section {
scroll-snap-align: start;
scroll-snap-stop: always;
}
Scroll padding and margin
If your container has a sticky header overlapping the top, scroll-padding shifts the effective snap boundary so content doesn’t end up hidden underneath it — the same idea as scroll-margin-top on an anchor target, applied to the whole snap container:
.gallery {
scroll-padding-top: 4rem; /* account for a sticky header */
}
scroll-margin does the equivalent per-child, useful when individual snap targets need their own offset rather than a container-wide one.
Scroll snap vs scroll-driven animations vs JavaScript carousels
| Scroll snap | Scroll-driven animations | JS carousel library | |
|---|---|---|---|
| Snaps to fixed positions | Yes, natively | No — animates continuously with scroll | Yes, manually implemented |
| Runs off the main thread | Yes | Yes | Usually no |
| Needs JavaScript | No | No | Yes |
| Good for | Galleries, full-page sections, tab-like panels | Progress bars, parallax, reveal effects tied to scroll position | Complex custom behavior (autoplay, infinite loop, dots/arrows) |
Scroll snap and scroll-driven animations solve different problems and combine well — you can snap a section into place and animate something continuously as the user scrolls toward it. Neither replaces a full carousel library if you need autoplay, infinite looping, or navigation dots; for that you still need JavaScript, but scroll snap handles the actual positioning so your script only needs to manage the extra UI.
Browser support and fallbacks
Scroll snap is broadly supported in current browsers, but because it’s purely additive — a scroll container without it just scrolls normally — there’s no meaningful fallback to write. If a browser doesn’t support one of the properties, the container simply scrolls freely instead of snapping, which is a reasonable degraded experience rather than a broken one.
The takeaway
Scroll snap replaces a category of JavaScript scroll-position code with two CSS properties: scroll-snap-type on the container and scroll-snap-align on its children. mandatory vs proximity controls how insistent the snapping is, and scroll-snap-stop: always prevents fast swipes from skipping points entirely. For galleries, full-page sections, and swipeable panels, it’s less code, smoother motion, and one less place for a bug to hide.
Tagged
Keep reading
Takina · · 4 min read CSS object-fit and object-position, Explained
object-fit controls how an image or video is cropped inside its box, and object-position controls which part of it stays visible. How they work together.
Takina · · 5 min read CSS inherit, initial, unset & revert Explained
CSS's four global keywords control where a property's value comes from. How inherit, initial, unset, and revert differ, with a comparison table.
Takina · · 4 min read Dynamic Viewport Units: dvh, svh, and lvh Explained
dvh, svh, and lvh fix the classic mobile vh bug where browser toolbars cut off full-height layouts. Here's what each unit measures and when to use it.