Why scroll snapping matters

Scrolling is the web's native way to navigate content that exceeds the viewport, and it works especially well for flat, list-like content structures. But scrolling has always had a precision problem: a scroll operation rarely ends cleanly at a paragraph boundary, a section heading, or the edge of an image. For paginated content and carousels, that imprecision leaves elements partially visible and the experience feeling uncontrolled.

Developers have traditionally reached for JavaScript to fix this. Yet script-based solutions lack access to the low-level scrolling primitives and composited scrolling behavior needed for a fully reliable result. The CSS Scroll Snap specification addresses this by moving scroll alignment into the browser, where it can be computed consistently and efficiently across platforms.

With scroll snap, an author marks a scroll container and its descendants with the boundaries where scrolling should settle. When the user finishes a scroll gesture, the browser picks the most appropriate end position—taking into account the scroll operation, the container layout, and the candidate snap positions—and animates smoothly to it.

Core concepts

Scroll snapping works by adjusting a scroll container's offset to a preferred snap position once a scroll operation completes. A container opts in via the scroll-snap-type property, which declares the axis (x, y, or both) and the snapping strictness (mandatory or proximity).

Snap positions are produced by descendant elements. An element declares its desired alignment with scroll-snap-align, which accepts start, end, or center for each axis:

  • start — the element's snap area start edge aligns with the snapport's start edge.
  • end — the element's snap area end edge aligns with the snapport's end edge.
  • center — the element is centered within the snapport.
Example of a various alignments on horizontal scrolling axis.

A typical image carousel is a natural fit. Setting the container to scroll-snap-type: x mandatory and each image to scroll-snap-align: center makes the carousel settle with an image centered on every scroll.

#gallery {
  scroll-snap-type: x mandatory;
  overflow-x: scroll;
  display: flex;
}

#gallery img {
   scroll-snap-align: center;
}
<div id="gallery">
  <img src="cat.jpg">
  <img src="dog.jpg">
  <img src="another_cute_animal.jpg">
</div>

Because snap positions attach to elements, the algorithm handles edge cases intelligently. If an image in the carousel is larger than the container itself, the specification requires the browser to detect the overflow and let the user pan freely within that image, snapping only at its edges. Without this safeguard, a mandatory snap could make part of the image unreachable.

Vertical sectioned page

For content organized into logical sections—like a product page—scroll-snap-type: y proximity is usually the more appropriate strictness setting. It doesn't force every scroll to land on a boundary; it only snaps when the user finishes close enough to one, preserving natural mid-section scrolling while still highlighting section changes.

article {
  scroll-snap-type: y proximity;
  /* Reserve space for header plus some extra space for sneak peeking. */
  scroll-padding-top: 15vh;
  overflow-y: scroll;
}
section {
  /* Snap align start. */
  scroll-snap-align: start;
}
header {
  position: fixed;
  height: 10vh;
}
<article>
  <header> Header </header>
  <section> Section One </section>
  <section> Section Two </section>
  <section> Section Three </section>
</article>

Scroll padding and margins

On a page with a fixed header, snapping section tops directly to the container's top edge would hide content behind the header. The scroll-padding property defines an inset against the scroll container's padding box, effectively shrinking the snapport. In the product page example, 15vh of top padding makes the browser treat a position 15vh below the top edge as the container's vertical start, so snapped sections leave space above in view.

The related scroll-margin property applies an equivalent outset to the snap target's box instead.

The names are deliberate: neither property is snap-specific. They adjust the relevant box for all scroll operations, including paged navigation (PageDown, PageUp) and Element.scrollIntoView() in Chrome.

Interplay with other scroll APIs

Snapping runs after every scroll operation, including script-driven ones. When you call an API such as Element.scrollTo, the browser computes the requested offset first and then applies snapping logic to determine where the scroll actually lands. Script doesn't have to replicate that calculation.

Smooth scrolling and scroll snap handle different aspects of a scroll operation—one controls its animation, the other its destination—so the two compose without conflict.

Overscroll behavior is unaffected by scroll snap; the two features govern different parts of the scrolling experience.

Practical caveats

Avoid mandatory snapping when snap positions are far apart from each other. Content sitting between positions can become impossible to access, which harms usability.

Scroll snap makes a good progressive enhancement. If you do need feature detection, test for the current specification properties rather than assuming support:

@supports (scroll-snap-align: start) {
  article {
    scroll-snap-type: y proximity;
    scroll-padding-top: 15vh;
    overflow-y: scroll;
  }
}
if (CSS.supports('scroll-snap-align: start')) {
  // use css scroll snap
} else {
  // use fallback
}

Avoid testing only for scroll-snap-type, which also appears in the old, deprecated specification and yields false positives.

Finally, do not assume that a programmatic scroll call always finishes at the requested offset. The snap algorithm may redirect the scroll to a nearby snap position after the call completes—an assumption that was already fragile before scroll snap, but is now less reliable still.

What's next

Scroll experience was the subject of a Chrome team survey that identified remaining gaps between CSS scroll behavior and plugin libraries. Planned work targeting scroll-snap includes:

  1. Improving API availability and cross-browser consistency.
  2. New CSS APIs such as scroll-start.
  3. New JavaScript events such as snapChanged().