CSS Scroll Snap Table

PieceWhat it doesField note
scroll-snap-type: x mandatoryEnables snap on scroll containerThe container declares; children define snap points
scroll-snap-align: startAligns child to start edgestart/center/end - where the child locks
scroll-snap-align: centerCenters child in viewportCarousel-style centering
scroll-padding: 16pxOffset the snap targetAccounts for sticky headers that cover the top
scroll-snap-stop: alwaysPrevents skipping childrenMandatory stop at EVERY item - no fast-flick-through
scroll-margin: 8pxGap between child and snap edgeThe child-side breathing room
proximity (vs mandatory)Snap when near, free-scroll otherwiseSofter UX for long content lists
Reference: the MDN scroll snap reference. scroll snap replaces the JS scroll-hijack pattern (wheel event preventDefault + scrollTo math) with native scrolling that respects the user's scroll physics, accessibility settings and touch momentum. The mandatory vs proximity choice is a UX decision: mandatory for carousels and image galleries, proximity for long lists where users sometimes want free scroll. Bottom line: the container declares scroll-snap-type, each child declares scroll-snap-align - the two-layer contract means children opt in individually and the container defines the axis and strictness. Related tools: overflow table (the scroll container setup), display table, and media queries table.

Scroll snap replaces the JS scroll-hijack pattern (wheel preventDefault + scrollTo math) with native scrolling that respects the user's scroll physics, accessibility settings and touch momentum. The table below is the working seven: the container declares, the children define snap points, and the padding attributes keep sticky headers from covering snap targets.

Bottom line: the container declares scroll-snap-type (axis + strictness), each child declares scroll-snap-align (which edge locks). The two-layer contract means children opt in individually and the container defines the axis and strictness - mandatory for carousels, proximity for long content lists.

The honest part: mandatory vs proximity is a UX decision, not a technical one. Mandatory always snaps (carousel feel: the user lands on an item every time), proximity snaps only when near (softer: the user can rest between items). The wrong choice makes users fight the scrolling - image galleries want mandatory, article card rows want proximity.

How to use

  1. Declare on the container: scroll-snap-type: x mandatory (or y proximity) - the axis and strictness in one line.
  2. Set each child's alignment: scroll-snap-align: start (or center) - where the child locks relative to the container edge.
  3. Add scroll-padding on the container (not margin on the child) to offset sticky headers that would cover the snap target.

Frequently asked questions

What is the difference between mandatory and proximity?

mandatory ALWAYS snaps: after every scroll gesture, the nearest child locks into place - the user cannot rest between items. proximity snaps only when NEAR a snap point - the user can stop between items. Image galleries and carousels want mandatory (every swipe shows an item); article card rows and long lists want proximity (the user controls the resting position). The wrong choice makes users fight the scrolling.

How do I build a horizontal card carousel with scroll snap?

Three lines: container { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; } and each card { scroll-snap-align: start; }. Add scroll-padding-inline-start to offset for any leading spacing. No JS, no library, no wheel-event preventDefault - the browser's native touch momentum, keyboard scrolling and accessibility all work because it is real scrolling, not hijacked scrolling.

Why does my sticky header cover the snap target?

Because the snap target is the container's top edge, and the sticky header overlays that edge. scroll-padding-top: 64px on the container offsets the snap position by 64px, so children lock 64px below the top edge - visible below the sticky header. The trap: scroll-padding is on the CONTAINER, while scroll-margin is on the CHILD - they add together but serve different purposes (container offset vs child breathing room).

What is scroll-snap-stop: always for?

It prevents fast-flicking through items: without it, a fast swipe can pass multiple snap points before settling. With scroll-snap-stop: always on each child, the scrolling stops at EVERY item - the user cannot skip past any. This matters for paginated content where each item is a page, and for accessibility (users with motor difficulties need to land on every stop). For long lists it is exhausting - use it only when each item demands individual attention.

Related tools