CSS scroll snap creates smooth, precise scrolling by snapping to defined points. Here's how to implement it.
Basic Setup#
1/* Container: enable scroll snapping */
2.scroll-container {
3 scroll-snap-type: x mandatory;
4 overflow-x: auto;
5 display: flex;
6}
7
8/* Children: define snap points */
9.scroll-item {
10 scroll-snap-align: start;
11 flex: 0 0 100%;
12}Scroll Snap Type#
1/* Horizontal mandatory snapping */
2.horizontal-mandatory {
3 scroll-snap-type: x mandatory;
4 overflow-x: auto;
5}
6
7/* Vertical mandatory snapping */
8.vertical-mandatory {
9 scroll-snap-type: y mandatory;
10 overflow-y: auto;
11}
12
13/* Proximity snapping (snaps only when close) */
14.proximity {
15 scroll-snap-type: x proximity;
16 overflow-x: auto;
17}
18
19/* Both axes */
20.both-axes {
21 scroll-snap-type: both mandatory;
22 overflow: auto;
23}
24
25/* No snapping */
26.none {
27 scroll-snap-type: none;
28}Scroll Snap Align#
1/* Align to start of container */
2.align-start {
3 scroll-snap-align: start;
4}
5
6/* Align to center */
7.align-center {
8 scroll-snap-align: center;
9}
10
11/* Align to end */
12.align-end {
13 scroll-snap-align: end;
14}
15
16/* Different alignment per axis */
17.mixed-align {
18 scroll-snap-align: start center; /* block inline */
19}
20
21/* No snap alignment */
22.no-snap {
23 scroll-snap-align: none;
24}Full-Page Sections#
1/* Vertical full-page scroll */
2.page-container {
3 height: 100vh;
4 overflow-y: auto;
5 scroll-snap-type: y mandatory;
6}
7
8.section {
9 height: 100vh;
10 scroll-snap-align: start;
11}
12
13/* With smooth scrolling */
14.page-container {
15 height: 100vh;
16 overflow-y: auto;
17 scroll-snap-type: y mandatory;
18 scroll-behavior: smooth;
19}Horizontal Carousel#
1/* Basic carousel */
2.carousel {
3 display: flex;
4 overflow-x: auto;
5 scroll-snap-type: x mandatory;
6 gap: 1rem;
7 padding: 1rem;
8
9 /* Hide scrollbar */
10 scrollbar-width: none;
11 -ms-overflow-style: none;
12}
13
14.carousel::-webkit-scrollbar {
15 display: none;
16}
17
18.carousel-item {
19 flex: 0 0 80%;
20 scroll-snap-align: center;
21 border-radius: 8px;
22}
23
24/* Multiple items visible */
25.multi-carousel {
26 display: flex;
27 overflow-x: auto;
28 scroll-snap-type: x mandatory;
29 gap: 1rem;
30}
31
32.multi-carousel-item {
33 flex: 0 0 calc(33.333% - 0.67rem);
34 scroll-snap-align: start;
35}Image Gallery#
1/* Gallery with centered images */
2.gallery {
3 display: flex;
4 overflow-x: auto;
5 scroll-snap-type: x mandatory;
6 scroll-padding: 1rem;
7}
8
9.gallery-image {
10 flex: 0 0 auto;
11 scroll-snap-align: center;
12 max-width: 90%;
13 max-height: 80vh;
14 object-fit: contain;
15}
16
17/* Vertical gallery */
18.vertical-gallery {
19 height: 100vh;
20 overflow-y: auto;
21 scroll-snap-type: y mandatory;
22}
23
24.vertical-gallery img {
25 width: 100%;
26 height: 100vh;
27 object-fit: cover;
28 scroll-snap-align: start;
29}Scroll Padding#
1/* Account for fixed header */
2.container {
3 scroll-snap-type: y mandatory;
4 scroll-padding-top: 80px; /* Header height */
5}
6
7.section {
8 scroll-snap-align: start;
9}
10
11/* Padding on all sides */
12.padded-snap {
13 scroll-snap-type: both mandatory;
14 scroll-padding: 2rem;
15}
16
17/* Inline padding for carousel */
18.carousel {
19 scroll-snap-type: x mandatory;
20 scroll-padding-inline: 2rem;
21}Scroll Snap Stop#
1/* Always stop at each item */
2.mandatory-stop {
3 scroll-snap-stop: always;
4 scroll-snap-align: start;
5}
6
7/* Allow skipping items with fast scroll */
8.normal-stop {
9 scroll-snap-stop: normal;
10 scroll-snap-align: start;
11}
12
13/* Useful for important content */
14.important-item {
15 scroll-snap-stop: always;
16 scroll-snap-align: center;
17}Card Grid#
1/* Snapping card grid */
2.card-grid {
3 display: grid;
4 grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
5 gap: 1.5rem;
6 max-height: 80vh;
7 overflow-y: auto;
8 scroll-snap-type: y proximity;
9 padding: 1rem;
10}
11
12.card {
13 scroll-snap-align: start;
14 background: white;
15 border-radius: 12px;
16 padding: 1.5rem;
17}Story/Reel Style#
1/* Vertical stories */
2.stories {
3 height: 100vh;
4 overflow-y: auto;
5 scroll-snap-type: y mandatory;
6 overscroll-behavior-y: contain;
7}
8
9.story {
10 height: 100vh;
11 scroll-snap-align: start;
12 scroll-snap-stop: always;
13 display: flex;
14 align-items: center;
15 justify-content: center;
16}
17
18/* Horizontal stories */
19.horizontal-stories {
20 display: flex;
21 width: 100%;
22 height: 100vh;
23 overflow-x: auto;
24 scroll-snap-type: x mandatory;
25 overscroll-behavior-x: contain;
26}
27
28.horizontal-story {
29 flex: 0 0 100%;
30 height: 100%;
31 scroll-snap-align: start;
32 scroll-snap-stop: always;
33}Tabs with Scroll Snap#
1/* Tab content with snapping */
2.tab-content {
3 display: flex;
4 overflow-x: auto;
5 scroll-snap-type: x mandatory;
6 scroll-behavior: smooth;
7}
8
9.tab-panel {
10 flex: 0 0 100%;
11 scroll-snap-align: start;
12 padding: 1rem;
13}
14
15/* Programmatic scrolling */
16/* tabPanel.scrollIntoView({ behavior: 'smooth' }) */Timeline#
1/* Horizontal timeline */
2.timeline {
3 display: flex;
4 overflow-x: auto;
5 scroll-snap-type: x proximity;
6 padding: 2rem;
7 gap: 4rem;
8}
9
10.timeline-item {
11 flex: 0 0 300px;
12 scroll-snap-align: center;
13}
14
15/* Vertical timeline */
16.vertical-timeline {
17 height: 600px;
18 overflow-y: auto;
19 scroll-snap-type: y proximity;
20 padding: 1rem;
21}
22
23.timeline-event {
24 scroll-snap-align: start;
25 padding: 2rem;
26 margin-bottom: 2rem;
27}Responsive Snapping#
1/* Mobile: full width snapping */
2.responsive-carousel {
3 display: flex;
4 overflow-x: auto;
5 scroll-snap-type: x mandatory;
6 gap: 1rem;
7}
8
9.carousel-item {
10 flex: 0 0 100%;
11 scroll-snap-align: start;
12}
13
14/* Tablet: partial width */
15@media (min-width: 768px) {
16 .carousel-item {
17 flex: 0 0 50%;
18 }
19}
20
21/* Desktop: smaller items, proximity snap */
22@media (min-width: 1024px) {
23 .responsive-carousel {
24 scroll-snap-type: x proximity;
25 }
26
27 .carousel-item {
28 flex: 0 0 33.333%;
29 }
30}With JavaScript#
1// Detect current snap item
2const container = document.querySelector('.scroll-container');
3const items = document.querySelectorAll('.scroll-item');
4
5container.addEventListener('scrollend', () => {
6 const containerRect = container.getBoundingClientRect();
7
8 items.forEach((item, index) => {
9 const itemRect = item.getBoundingClientRect();
10 const inView = itemRect.left >= containerRect.left &&
11 itemRect.right <= containerRect.right;
12
13 if (inView) {
14 console.log('Current item:', index);
15 item.classList.add('active');
16 } else {
17 item.classList.remove('active');
18 }
19 });
20});
21
22// Scroll to specific item
23function scrollToItem(index) {
24 items[index].scrollIntoView({
25 behavior: 'smooth',
26 inline: 'start',
27 });
28}Accessibility#
1/* Ensure focus visibility */
2.scroll-item:focus-visible {
3 outline: 3px solid #3b82f6;
4 outline-offset: 2px;
5}
6
7/* Reduce motion */
8@media (prefers-reduced-motion: reduce) {
9 .scroll-container {
10 scroll-behavior: auto;
11 }
12}
13
14/* Keyboard navigation */
15.scroll-container {
16 scroll-snap-type: x mandatory;
17}
18
19.scroll-item {
20 scroll-snap-align: start;
21}
22
23.scroll-item:focus {
24 /* Ensure focused item is visible */
25 scroll-margin-inline: 1rem;
26}Best Practices#
Container Setup:
✓ Set overflow direction
✓ Choose mandatory vs proximity
✓ Add scroll-padding for fixed elements
✓ Consider scroll-behavior
Item Setup:
✓ Define flex/grid sizing
✓ Set scroll-snap-align
✓ Use scroll-snap-stop for important items
✓ Add scroll-margin if needed
UX Considerations:
✓ Proximity for optional snapping
✓ Mandatory for full-page sections
✓ Test with keyboard/touch
✓ Respect reduced motion
Avoid:
✗ Snapping on large content areas
✗ Missing overflow settings
✗ Ignoring accessibility
✗ Mandatory snap on long content
Conclusion#
CSS scroll snap creates intuitive, native-feeling scroll experiences without JavaScript. Use scroll-snap-type on containers with mandatory for strict snapping or proximity for gentle guidance. Apply scroll-snap-align to children to define snap points. Combine with scroll-padding for fixed headers and scroll-snap-stop to ensure important content isn't skipped. Always respect user motion preferences and ensure keyboard accessibility.