Scroll snap: CSS-native carousels
Scroll snapping gives users the satisfying "click into place" feel of native apps. Two properties on the container, one on each child.
The two-part API
Scroll snap requires properties on both the scroll container and its children. The container declares the snap axis and strictness; each child declares where it should align:
/* 1. The scroll container */
.carousel {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
gap: 1rem;
}
/* 2. Each snap target */
.carousel > .slide {
scroll-snap-align: start;
flex: 0 0 100%;
}
The scroll container must have an overflow value that creates a scrollport — auto, scroll, or hidden.
mandatory vs proximity
The scroll-snap-type strictness value controls how aggressively the browser snaps:
/* mandatory: always snaps to the nearest point */
/* User cannot rest between snap points */
.carousel { scroll-snap-type: x mandatory; }
/* proximity: snaps only when close to a snap point */
/* User can stop anywhere — snapping is a suggestion */
.gallery { scroll-snap-type: y proximity; }
/* Both axes */
.grid-scroll { scroll-snap-type: both mandatory; }
mandatory for full-page sections and carousels where partial views don't make sense. Use proximity for long lists where the user needs freedom to scroll naturally.Snap alignment options
The scroll-snap-align property controls which edge of the child aligns with the container's snap position:
/* Align the start edge of each child with the container's start */
.slide { scroll-snap-align: start; }
/* Center each child in the viewport */
.slide { scroll-snap-align: center; }
/* Align the end edge */
.slide { scroll-snap-align: end; }
/* Different alignment per axis: block inline */
.slide { scroll-snap-align: start center; }
/* Remove a child from snapping */
.no-snap { scroll-snap-align: none; }
Scroll padding and margin
Use scroll-padding on the container to offset snap positions (for example, to account for a sticky header). Use scroll-margin on individual children to adjust their snap offset:
/* Container: offset snap point below sticky header */
.carousel {
scroll-snap-type: x mandatory;
scroll-padding-inline: 2rem;
}
/* Children: adjust individual snap offset */
.slide {
scroll-snap-align: start;
scroll-margin-inline-start: 1rem;
}
The scroll-padding shorthand follows the same syntax as padding — one to four values for each edge.
Horizontal gallery pattern
A peek-through gallery where the next and previous slides are partially visible, encouraging the user to scroll:
.gallery {
display: flex;
gap: 1rem;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-padding-inline: 2rem;
padding-inline: 2rem;
scrollbar-width: none; /* hide scrollbar */
}
.gallery > img {
flex: 0 0 calc(80% - 0.5rem);
scroll-snap-align: center;
aspect-ratio: 16 / 9;
object-fit: cover;
border-radius: 0.75rem;
}
scroll-padding when using padding on the scroll container — otherwise snap points won't account for the padding offset.Vertical full-page sections
Scroll snap works on the vertical axis too. Full-page section layouts are a natural fit:
html {
scroll-snap-type: y mandatory;
}
.page-section {
scroll-snap-align: start;
min-block-size: 100dvh;
display: grid;
place-items: center;
}
/* Stop snapping — let user scroll freely */
.page-section--long {
scroll-snap-align: none;
}
Be cautious with mandatory on the root element — users with trackpads or accessibility needs may find it frustrating when they can't scroll freely past content.