w3tweaks.com · CSS Tutorial

CSS scroll-snap

Build carousels, full-page scrollers, and sliders — zero JavaScript.

Tab 1

Interactive Scroll-Snap Builder

Toggle axis, strictness, and alignment — the demo and generated code update live. Drag to scroll inside the preview.

Live preview scroll-snap-type: x mandatory
Slide 1
Slide 2
Slide 3
Slide 4
Slide 5
/* Container */
.container {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding: 0;
}
.item {
  flex: 0 0 100%;
  scroll-snap-align: start;
}
mandatory vs proximity: mandatory always snaps to the next snap point — the scroll never rests between items. Best for carousels and full-page layouts where every slide should be fully visible. proximity only snaps if you're already near a snap point — the scroll can rest freely between items. Best for long articles and galleries where users may want to read mid-section.
Tab 2

5 Real-World Patterns

These are the actual use cases. Scroll inside each demo — all pure CSS, zero JavaScript.

🎠 Carousel with synced dots

Dots sync via scrollsnapchange event (Chrome 129+/Safari 18.2+) — replaces IntersectionObserver hacks on supported browsers.

👥 Horizontal friend/avatar list
😊
🎯
🔥
🎨
🚀
💡
🎸

scroll-snap-align: center on each avatar — clicking an avatar centers it. scroll-padding ensures edge avatars are reachable.

📄 Full-page vertical sections
Hero Section
scroll-snap-type: y mandatory
Features
scroll-snap-align: start
Pricing
100% CSS — no fullPage.js
Contact
Use proximity on long pages

Replaces JavaScript libraries like fullPage.js for simple landing pages. Use proximity on content-heavy pages to avoid trapping readers.

💬 Chat — keep scrolled to bottom
Hey! Did you see the new CSS scroll-snap tutorial?
Not yet, is it good?
It's great! No JavaScript needed.
Wait, really? No IntersectionObserver?
Nope! scrollsnapchange handles the dots.
That's so clean. Adding to w3tweaks now.
Latest message always snaps into view ↓

Only the last message has scroll-snap-align: end. As new messages are added, the list auto-scrolls to keep it in view.

🗺️ 2D grid (both axes)
1,1
1,2
1,3
1,4
2,1
2,2
2,3
2,4
3,1
3,2
3,3
3,4

scroll-snap-type: both proximity — snaps on both X and Y axes simultaneously. Perfect for map-like or grid-based interfaces.

Tab 3

Advanced: Events, Stop & scroll-padding

The features nobody else explains — scroll-snap-stop, the new scrollsnapchange event, and fixing the sticky header problem.

scroll-snap-stop: always — prevent fast-swipe skipping

Without scroll-snap-stop, a fast swipe can skip multiple slides. With always, the browser must stop at each snap point.

1
2
3
4
5
.slide {
  scroll-snap-align: start;
  scroll-snap-stop: always;
}
scrollsnapchange — sync UI without IntersectionObserver

New in Chrome 129+ / Safari 18.2+. Fires when the snap target changes — perfect for syncing pagination dots, labels, or counters.

Slide 1 / 4 — scroll to see event fire
// Chrome 129+ / Safari 18.2+
carousel.addEventListener('scrollsnapchange', e => {
  // e.snapTargetInline = snapped element
  updateDots(e.snapTargetInline);
});
scroll-padding-top — fix the sticky header problem

Without scroll-padding-top, snap targets slide behind a sticky header. Set it to the header height.

📌 Sticky Nav (40px)
Section 1 — visible ✓
Section 2 — visible ✓
Section 3 — visible ✓
.container {
  scroll-snap-type: y mandatory;
  scroll-padding-top: 40px; ← nav height
}
scroll-margin vs scroll-padding

The most confused pair in scroll-snap. They do different things from different sides:

scroll-padding → on the container
Shrinks the snap port from the inside. Useful for headers.
scroll-margin → on the child
Pushes the snap point outward. Useful for offset individual items.
/* Container — push snap port inward */
.container { scroll-padding-top: 60px; }

/* Child — push snap point outward */
.section { scroll-margin-top: 20px; }
Accessibility warning — use mandatory carefully: scroll-snap-type: mandatory can trap keyboard and screen reader users if snap targets are widely spaced and content between them becomes unreachable. Always test with keyboard navigation. Use proximity for content-heavy pages. Add a skip link or ensure all content is reachable via the tab key.
Read the tutorial