Back to Blog
CSSscroll-snapScrollingUX

CSS Scroll Snap Guide

Master CSS scroll snap for creating smooth, precise scrolling experiences with snap points.

B
Bootspring Team
Engineering
July 22, 2019
6 min read

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}
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}
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.

Share this article

Help spread the word about Bootspring