What is Scroll snap?
Scroll snap: CSS scroll snap makes a scroll container stop at defined points, such as the start of each slide, so carousels and galleries land cleanly without JavaScript.
Scroll snap turns an ordinary scrolling container into one that settles on its children. The container declares scroll-snap-type with an axis and a strictness, each child declares where it wants to snap with scroll-snap-align, and the browser does the rest: when scrolling ends, the view eases to the nearest snap point. It gives carousels, image galleries, product rails and paginated panels a native feel on touch and trackpads, with the browser's own scroll physics, momentum and accessibility, instead of a JavaScript slider that fights them.
The properties
.rail {
display: flex;
gap: 1rem;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-padding-inline: 1rem; /* where the snap edge sits, inside the padding */
overscroll-behavior-x: contain;
}
.rail > * {
flex: 0 0 80%;
scroll-snap-align: start;
scroll-snap-stop: always; /* never skip a slide on a fast fling */
}
- `mandatory` always lands on a snap point; `proximity` snaps only when the scroll ends near one. Use proximity for long content where mandatory would trap the reader between tall sections.
- `scroll-snap-align`:
start,centerorendof the child aligns with the container's snap area. - `scroll-padding` on the container and `scroll-margin` on children adjust where the snap edge is, for example to leave a sticky header visible.
- `scroll-snap-stop: always` stops at every child even on a fast swipe, which carousels want and long lists do not.
Pitfalls
- Full-page mandatory snapping between viewport-height sections. On short screens or with large fonts, a section taller than the viewport becomes impossible to read in full, and the hijacked scroll annoys people. Use proximity, or do not snap the page.
- Children larger than the container on the snap axis combine badly with mandatory; the browser snaps to the start and the overflow is hard to reach.
- Previous and Next buttons should scroll by one child's width with
scrollBy, andscroll-behavior: smoothshould be turned off under prefers-reduced-motion. - Hidden scrollbars remove the only visual hint that content continues. If you hide them, show dots, a count or a partially visible next item.
Accessibility
A snapping rail is still a scroll container, so keyboard users can reach it only if it is focusable: give it tabIndex={0}, a role="region" and an aria-label such as "Featured products", and the arrow keys then scroll it. Provide buttons for Previous and Next so users who cannot swipe or scroll horizontally can move through it. Each slide should be real content, with headings and links, not a background image with text baked in. Autoplay needs a pause control and should stop while the rail is hovered or focused.
In Tailwind CSS and MiniDev
Tailwind's snap-x, snap-mandatory, snap-proximity, snap-start, snap-center and snap-always utilities map directly onto the properties. MiniDev's Carousel is a snap-x snap-mandatory flex rail with snap-start slides at 80, 45 and 30 percent width across breakpoints, a labeled region that takes focus, Arrow key handling, and Previous and Next buttons that scroll by 85 percent of the rail's width:
import { Carousel } from "@/components/ui/carousel"
export function Featured({ slides }: { slides: { id: string; title: string }[] }) {
return (
<Carousel label="Featured products">
{slides.map((s) => (
<article key={s.id} className="rounded-xl border border-border p-4">
<h3 className="font-medium">{s.title}</h3>
</article>
))}
</Carousel>
)
}
HorizontalProductRail applies the same mechanics to a shop rail, ImageGallery is the grid counterpart for photos, and ScrollArea styles a scrolling panel without snapping. For lists that grow as you scroll, see infinite scroll; for lists too long to render at all, virtual scrolling.
CarouseluiScroll snap React carousel with previous and next buttons, arrow key support and a labelled region. Shows one, two or three slides by breakpoint, no library.npx shadcn@latest add @minidev/carouselHorizontal Product RailmotionA horizontally scrolling rail of tagged product cards that slide in and lift on hover. The rail is a focusable, labelled region for keyboard scrolling.npx shadcn@latest add @minidev/horizontal-product-rail