Articles

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.

Takina Takina · · 3 min read
Close-up of CSS code in an editor

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-type goes on the scroll container. The first value is the axis (x, y, or both); the second is strictness. mandatory means the browser always settles on a snap point after a scroll gesture ends. proximity only snaps if the scroll stopped near a snap point — if the user stops mid-scroll, it leaves the position alone.
  • scroll-snap-align goes on each child, declaring which edge of that child aligns with the container: start, end, or center.
.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 snapScroll-driven animationsJS carousel library
Snaps to fixed positionsYes, nativelyNo — animates continuously with scrollYes, manually implemented
Runs off the main threadYesYesUsually no
Needs JavaScriptNoNoYes
Good forGalleries, full-page sections, tab-like panelsProgress bars, parallax, reveal effects tied to scroll positionComplex 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.

Takina 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.

#CSS #Web Development #Frontend
Takina 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.

#CSS #Web Development #Frontend
Takina 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.

#CSS #Web Development #Frontend