More Scroll Snippets
Scroll Snap Gallery — Free HTML CSS JS Snippet
Scroll Snap Gallery · Scroll · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Scroll Snap Gallery — scroll-snap-type, scroll-snap-align & IntersectionObserver Dots

A scroll snap gallery provides smooth, paginated horizontal scrolling where each slide snaps into view — like an image carousel or coverflow carousel but driven by native browser scroll rather than JavaScript animation. Wire each slide to a photo gallery or lightbox for full-size viewing. It works naturally with touch swipe on mobile and mouse drag on desktop.
The CSS scroll snap
.gallery { scroll-snap-type: x mandatory; overflow-x: auto; scroll-behavior: smooth } sets the container as a horizontal snap container. mandatory means the browser will always snap to a snap point after scrolling stops — it will never rest between slides. Each .slide { scroll-snap-align: center } declares each slide as a snap target.
The dot navigation
Clicking a dot calls gallery.scrollTo({ left: slides[i].offsetLeft, behavior: 'smooth' }). offsetLeft is the slide's distance from its parent container left edge — scrolling to this position aligns the slide's left edge with the container's left edge.
IntersectionObserver active dot
An IntersectionObserver with threshold: 0.5 watches each slide. When a slide is more than 50% visible, its corresponding dot gets the .active class. root: gallery scopes the observer to the gallery container rather than the viewport. This automatically updates the active dot when the user scrolls manually.
Hiding the scrollbar
scrollbar-width: none (Firefox) and ::-webkit-scrollbar { display: none } (Chrome/Safari) hide the scrollbar, giving the gallery a clean appearance. Scroll interaction still works; only the visual scrollbar is hidden.
How CSS scroll snap works
The gallery container has scroll-snap-type: x mandatory on the x-axis. Each card has scroll-snap-align: start. When the user scrolls and releases, the browser automatically snaps the scroll position to the nearest card's start edge. mandatory means the snap always happens — optional allows free scrolling between snaps. The scroll uses overflow-x: auto and a hidden scrollbar via ::-webkit-scrollbar { display: none }.
The dot indicator sync
A scroll event listener reads scrollLeft / scrollWidth * numCards to compute the approximate active index. It then updates the dot indicators — the active dot gets the filled style. requestAnimationFrame is used to throttle the update to one per paint. For more accurate snap detection, use IntersectionObserver on each card with a 0.5 threshold.
Keyboard navigation
The left/right arrow buttons call scrollBy with the card width and smooth behaviour: container.scrollBy({ left: cardWidth, behavior: 'smooth' }). Prev/next buttons disable at the boundaries when scroll position is at 0 or maximum. Keyboard arrow keys can also be wired to the same scrollBy calls for accessibility.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You don't have to work out the snap and index math by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why the active dot is computed with Math.round(gallery.scrollLeft / gallery.clientWidth) instead of tracking an index variable directly, or why scroll-snap-type mandatory guarantees the gallery never rests between two slides the way proximity would allow. The same assistant can help optimize it — asking whether the plain scroll listener should be throttled with requestAnimationFrame for very fast flicks, or whether IntersectionObserver would give more reliable active-dot detection than the scrollLeft division. It's also useful for extending the effect: ask it to add left/right arrow buttons that call scrollBy with the slide width, support keyboard arrow-key navigation, or make the gallery loop back to the first slide after the last. Treat the code less like a finished artifact and more like a starting point for a conversation.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a "scroll snap gallery" in plain HTML, CSS, and JavaScript using only native CSS scroll snap — no carousel library, no third-party JS.
Requirements:
- A horizontally scrollable container with overflow-x set to auto, scroll-snap-type set to x mandatory, and scroll-behavior set to smooth, containing several full-width slide elements each with scroll-snap-align set to start, and flex-shrink set to 0 so slides never compress.
- Hide the native scrollbar visually (scrollbar-width: none plus the ::-webkit-scrollbar display: none pseudo-element) while keeping the container fully scrollable by touch, mouse drag, or trackpad.
- Generate one dot indicator per slide dynamically in JavaScript by looping over the slide elements (do not hardcode the dots in markup), with the first dot marked active initially.
- Clicking a dot must scroll the gallery to that slide's position using scrollTo with the slide's offsetLeft and smooth behavior.
- On every scroll event of the gallery container, compute the currently visible slide index by dividing the container's current scrollLeft by its clientWidth and rounding to the nearest whole number, then update which dot has the active class to match.
- Confirm the dot generation logic automatically produces the correct number of dots if a slide is added or removed from the markup, with no other code changes needed.Step by step
How to Use
- 1Scroll or click dotsScroll horizontally or click the dots to navigate. Each slide snaps into position. Swipe on mobile.
- 2Update slide contentIn the HTML panel, change the gradient background, emoji, title, and description in each .slide div.
- 3Add more slidesCopy a .slide div and paste it inside .gallery. A new dot is generated automatically via the JS querySelectorAll forEach.
- 4Change snap alignmentUpdate scroll-snap-align: center to start or end on .slide in the CSS.
- 5Change the snap strictnessChange scroll-snap-type: x mandatory to x proximity for snapping only when near a snap point.
- 6Export in your formatClick "HTML" for a standalone file, "JSX" for a React component, or "Tailwind" for a React + Tailwind version.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
scroll-snap-type on a scroll container declares it as a snap context. scroll-snap-align on children declares each as a snap point. The browser snaps to the nearest (proximity) or always (mandatory) snap point after scrolling stops.
mandatory snaps after every scroll — the gallery never rests between slides. proximity only snaps when the scroll position is close enough to a snap point. mandatory is better for full-slide carousels; proximity is better for partially-visible item lists.
Each slide is observed with threshold: 0.5 and root: gallery. When a slide is more than 50% within the gallery viewport, the observer fires with isIntersecting: true and the matching dot gets .active.
Copy a .slide div and paste it inside .gallery. The JS querySelectorAll(".slide") picks it up and creates a new dot automatically. The IntersectionObserver observes all slides via querySelectorAll as well.
Yes. CSS scroll snapping is supported natively on all modern mobile browsers. Touch swipe triggers the native scroll which then snaps to the nearest slide.
Yes. Click "JSX" for a React component. The CSS scroll snap properties work identically in React. Use useRef on the gallery container and useEffect with an IntersectionObserver to manage the active dot state.