webdevpuneet.com

Native Scroll-Snap Carousel — HTML CSS JS Snippet

Native Scroll-Snap Carousel · Carousels · Plain HTML, CSS & JS · Live preview

Copy & paste

Source Code

<div class="nss-wrap">
  <div class="nss-track" id="nssTrack">
    <div class="nss-slide" style="background:linear-gradient(160deg,#6366f1,#4338ca)">1</div>
    <div class="nss-slide" style="background:linear-gradient(160deg,#0ea5e9,#0369a1)">2</div>
    <div class="nss-slide" style="background:linear-gradient(160deg,#ec4899,#9d174d)">3</div>
    <div class="nss-slide" style="background:linear-gradient(160deg,#10b981,#047857)">4</div>
    <div class="nss-slide" style="background:linear-gradient(160deg,#f59e0b,#b45309)">5</div>
  </div>
  <div class="nss-dots" id="nssDots"></div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Sliding is 100% native CSS scroll-snap — no JavaScript translate math or drag handling at all
Free momentum, touch, trackpad, and keyboard scrolling inherited directly from the browser
Dot clicks use scrollIntoView, which automatically respects the same scroll-snap-align as native scrolling
IntersectionObserver (not a scroll listener) detects the settled slide, avoiding any layout-thrashing scroll math
Dots stay accurate even when a user scrolls manually, not just when clicking a dot
Scrollbar hidden cross-browser while the track remains fully scrollable by any input method

About this UI Snippet

Native Scroll-Snap Carousel — The Browser Does the Sliding

Screenshot of the Native Scroll-Snap Carousel snippet rendered live

Every other slide-based carousel in this library computes its own translateX and handles its own drag/momentum. This one does neither — scroll-snap-type: x mandatory on the track and scroll-snap-align: center on each slide tell the *browser's own scrolling engine* to snap to slide boundaries, which means momentum, touch, trackpad, and keyboard scrolling are all free, genuinely native behaviors, not reimplemented ones.

JavaScript only ever watches, never moves

With the sliding itself fully delegated to CSS, the JS in this snippet has exactly two jobs: let a dot click *request* a scroll (via scrollIntoView, which respects the same snap alignment automatically) and *observe* which slide the browser has actually settled on afterward, via IntersectionObserver. Neither job ever writes a transform or tracks a pointer position — there's no "carousel state" to keep in sync with the DOM at all, because the DOM's own scroll position *is* the state.

Why IntersectionObserver instead of a scroll listener

Detecting "which slide is currently active" from a raw scroll event means computing scroll offsets on every single scroll frame — exactly the kind of layout-thrashing work IntersectionObserver exists to avoid. Here, each slide is watched with a threshold: [0.6], so the observer's callback only fires when a slide crosses 60% visibility — a natural, cheap way to say "this one is now the settled slide," whether the scroll was triggered by a button, a swipe, or a trackpad.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSFive full-width slides appear, with the first dot highlighted.
  2. 2
    Swipe or scroll the trackNative momentum scrolling moves between slides, snapping cleanly to each one — no JS drag logic involved.
  3. 3
    Click a dotThe browser smoothly scrolls to that slide via scrollIntoView, which honors the same snap alignment.
  4. 4
    Scroll with a trackpad or mouse wheelWorks identically, since it's genuinely native scrolling, not a simulated gesture.
  5. 5
    Add a sixth slideAdd one .nss-slide div — a matching dot is generated automatically and observed for snap detection.

Real-world uses

Common Use Cases

Mobile-first product or image galleries
The lightest possible carousel implementation, since the browser's own scroll engine does the heavy lifting.
Onboarding screens
Native swipe feel with zero custom gesture code — exactly how a platform-native onboarding flow behaves.
Simple hero or promo carousels
When a project doesn't need drag momentum tuning or exotic transitions, this is the leanest correct option.
Performance-sensitive pages
No JavaScript animation loop or drag listener running at all — just two lightweight, purely reactive helpers.

Got questions?

Frequently Asked Questions

For a plain "one slide at a time, swipeable" carousel, native scroll-snap gives momentum, touch, and keyboard scrolling for free from the browser — a JS-driven carousel has to reimplement all three by hand for equivalent behavior, which is only worth it when you need effects scroll-snap can't express (3D, elastic, magnetic, etc).

Change .nss-slide's flex-basis from 100% to a fraction like 50% or 33.33% — scroll-snap-align still works per-slide, so partial-width slides snap just as cleanly.

Yes — give each arrow a click handler that calls track.scrollBy({ left: track.clientWidth, behavior: "smooth" }) (or negative for previous); the snap points still apply since it's still native scrolling.

scrollIntoView already knows to align to the slide's scroll-snap-align setting and animates via the browser's own smooth-scroll implementation — hand-calculating an offset would just be reproducing logic the browser already provides correctly.

Yes — because scrolling is native, the track is keyboard-scrollable by default (Tab into it, then arrow keys or Page Up/Down), and dots are real labeled buttons offering an additional, more discoverable navigation path.