|$ curl https://forge-ai.dev/api/markdown?path=docs/css/scroll-snap
$cat docs/css-scroll-snap.md
updated This Week·10 min read·published

CSS Scroll Snap

CSSScroll SnapUXIntermediate
Introduction

CSS Scroll Snap allows you to control scroll behavior by defining snap points — positions where the scrollport locks into place. This creates polished, app-like scrolling experiences for carousels, image galleries, section-by-section pages, and horizontal timelines.

Scroll Snap is entirely CSS-driven, requiring no JavaScript for the snap logic. It works on both horizontal and vertical scroll containers and provides fine-grained control over snap alignment and stopping behavior.

scroll-snap-basics.css
CSS
1.scroll-container {
2 scroll-snap-type: x mandatory;
3 overflow-x: auto;
4 display: flex;
5 gap: 16px;
6}
7
8.scroll-item {
9 scroll-snap-align: start;
10 flex: 0 0 300px;
11}
scroll-snap-type

The scroll-snap-type property is set on the scroll container. It defines the axis and snap strictness: whether snapping is required (mandatory) or suggested (proximity).

scroll-snap-type.css
CSS
1.container {
2 /* Axis */
3 scroll-snap-type: none; /* default — no snapping */
4 scroll-snap-type: x; /* horizontal snapping */
5 scroll-snap-type: y; /* vertical snapping */
6 scroll-snap-type: both; /* both axes */
7 scroll-snap-type: block; /* block axis (respects writing mode) */
8 scroll-snap-type: inline; /* inline axis */
9
10 /* Strictness */
11 scroll-snap-type: x mandatory; /* must snap to the nearest point */
12 scroll-snap-type: y proximity; /* snaps only when close enough */
13 scroll-snap-type: both mandatory;
14}

warning

Use mandatory carefully — if content is larger than the viewport, the user may become stuck unable to scroll past certain items. Reserve mandatory for cases where each snap item fits within the scrollport.
scroll-snap-align

Set on child elements, scroll-snap-align defines where the element snaps to within the scroll container — at its start, center, or end.

scroll-snap-align.css
CSS
1.item {
2 scroll-snap-align: start; /* snaps to the start of the container */
3 scroll-snap-align: center; /* centers in the container */
4 scroll-snap-align: end; /* snaps to the end of the container */
5 scroll-snap-align: none; /* excluded from snapping */
6}
7
8/* Different items can have different alignment */
9.item:first-child { scroll-snap-align: start; }
10.item:last-child { scroll-snap-align: end; }
11.item:not(:first-child):not(:last-child) { scroll-snap-align: center; }
preview
scroll-snap-stop

Controls whether the scroll container is allowed to pass over snap positions during a fast scroll gesture. By default (normal), fast scrolls can skip items. Setting always forces the scroll to stop at every snap position.

scroll-snap-stop.css
CSS
1.item {
2 scroll-snap-stop: normal; /* default — can skip during fast scrolls */
3 scroll-snap-stop: always; /* force stop at this snap position */
4}

info

Use scroll-snap-stop: always for section-by-section page layouts where skipping content would be disorienting. For carousels and galleries, normal provides a better user experience.
scroll-margin

scroll-margin adds space around a snap target, pushing the snap position inward. This is useful for creating visual breathing room or accounting for fixed headers.

scroll-margin.css
CSS
1.item {
2 scroll-snap-align: start;
3 scroll-margin: 20px; /* 20px inset on all sides */
4 scroll-margin-top: 60px; /* account for fixed header */
5 scroll-margin-inline: 24px; /* logical property */
6}
preview
scroll-padding

scroll-padding is set on the scroll container and defines the offset that snap positions must respect. This is ideal for ensuring content is visible past fixed headers or toolbars.

scroll-padding.css
CSS
1.scroll-container {
2 scroll-snap-type: y mandatory;
3 scroll-padding-top: 80px; /* snap starts 80px below top */
4 scroll-padding: 80px 0 0 0; /* shorthand */
5 scroll-padding-block-start: 80px; /* logical property */
6}
7
8/* With sticky header — content snaps below the header */
9.header {
10 position: sticky;
11 top: 0;
12 height: 60px;
13}
14
15.main {
16 scroll-snap-type: y mandatory;
17 scroll-padding-top: 60px;
18}
19
20.section {
21 scroll-snap-align: start;
22 min-height: 100vh;
23}
Horizontal & Vertical Snap

Scroll Snap works on both axes. The patterns differ slightly based on the layout direction.

Horizontal Snap (Carousel)

horizontal-snap.css
CSS
1.carousel {
2 display: flex;
3 overflow-x: auto;
4 scroll-snap-type: x mandatory;
5 gap: 16px;
6 scroll-padding: 0 24px;
7}
8
9.carousel-item {
10 flex: 0 0 80%;
11 scroll-snap-align: start;
12 max-width: 400px;
13}

Vertical Snap (Full Page)

vertical-snap.css
CSS
1.fullpage {
2 height: 100vh;
3 overflow-y: auto;
4 scroll-snap-type: y mandatory;
5 scroll-snap-stop: always;
6}
7
8.fullpage-section {
9 height: 100vh;
10 scroll-snap-align: start;
11 display: flex;
12 align-items: center;
13 justify-content: center;
14}
preview
Mandatory vs Proximity

The strictness value determines how aggressively the scrollport snaps to positions.

ValueBehaviorBest For
mandatoryAlways snaps to the nearest snap position when scrolling stopsCarousels, galleries, step-by-step wizards
proximityOnly snaps if the scroll position is close enough to a snap pointLong pages, scrolling lists, content that needs free scrolling
📝

note

Proximity is the safer choice for unknown content sizes. Mandatory can trap users if an item is larger than the scrollport — the browser cannot snap away from it.
Practical Examples

Image Carousel

carousel.css
CSS
1.carousel {
2 display: flex;
3 overflow-x: auto;
4 scroll-snap-type: x mandatory;
5 scroll-behavior: smooth;
6 -webkit-overflow-scrolling: touch;
7 gap: 8px;
8 padding: 16px;
9}
10
11.carousel img {
12 flex: 0 0 100%;
13 scroll-snap-align: center;
14 object-fit: cover;
15 border-radius: 8px;
16}
17
18/* Hide scrollbar */
19.carousel {
20 scrollbar-width: none;
21}
22.carousel::-webkit-scrollbar { display: none; }

Section-by-Section Scrolling

section-scroll.css
CSS
1.site-container {
2 height: 100vh;
3 overflow-y: auto;
4 scroll-snap-type: y mandatory;
5 scroll-snap-stop: always;
6}
7
8.section {
9 height: 100vh;
10 scroll-snap-align: start;
11 display: flex;
12 flex-direction: column;
13 justify-content: center;
14 padding: 80px;
15}
16
17/* Progress indicator for snap sections */
18.section::after {
19 content: '';
20 position: fixed;
21 right: 24px;
22 top: 50%;
23 transform: translateY(-50%);
24 width: 8px;
25 height: 8px;
26 border-radius: 50%;
27 background: #00FF41;
28 opacity: 0.3;
29}
30
31.section:nth-child(1):target::after { opacity: 1; }

Terminal-Like Paging

terminal-paging.css
CSS
1.terminal-output {
2 height: 80vh;
3 overflow-y: auto;
4 scroll-snap-type: y proximity;
5 background: #0D0D0D;
6 border: 1px solid #222222;
7 border-radius: 8px;
8 font-family: 'JetBrains Mono', monospace;
9 font-size: 13px;
10}
11
12.log-line {
13 scroll-snap-align: start;
14 padding: 4px 16px;
15 border-bottom: 1px solid #1A1A1A;
16}
17
18.log-line:hover {
19 background: #1A1A2E;
20}
preview
Browser Support
BrowserSupportVersion
Chrome69+
Firefox68+
Safari11+
Edge79+
$Blueprint — Engineering Documentation·Section ID: CSS-31·Revision: 1.0