While CSS Scroll Snap provides native alignment points for responsive carousels and paginated presentations, mobile touch gestures and macOS trackpad velocity can introduce unpredictable navigation behavior. By default, browsers allow “fast-flick” kinetic scrolling gestures to skip past multiple snap positions, settling on a distant card several slides ahead. Standardised in the CSS Scroll Snap Module Level 1, the scroll-snap-stop property provides developers with precise control over pagination momentum. Specifying scroll-snap-stop: always forces the scroll engine to halt kinetic momentum at the very next snap target, creating a strict card-by-card pagination experience.
Understanding scroll-snap-stop: normal vs always
The scroll-snap-stop property is declared directly on snap target children inside a scroll container:
scroll-snap-stop: normal: Default behavior. The browser is permitted to pass over this snap target if the user’s flick or swipe gesture carries sufficient kinetic momentum. Suitable for long text feeds where users want to rapidly skip through content sections.scroll-snap-stop: always: The scroll container must halt momentum at this snap target even if the user performs a high-velocity swipe. Essential for multi-step onboarding wizards, product configurators, and mobile app story interfaces where skipping intermediate steps breaks user flow.- Interaction with Touch Momentum: On iOS Safari and macOS trackpads,
alwaysdampens inertia scrolling calculations, guaranteeing deterministic single-item step transitions.
Implementing Strict Single-Item Card Pagination in CSS
Here is a production implementation of a horizontal pagination track enforcing single-card step navigation:
/* 1. Scroll Snap Track Container */
.pagination-container {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch;
gap: 20px;
padding: 32px;
/* Center active slide within viewport */
scroll-padding: 0 32px;
}
/* 2. Child Cards Enforcing Mandatory Halt */
.pagination-card {
flex: 0 0 calc(100% - 64px);
max-width: 480px;
scroll-snap-align: center;
/* Force browser to stop at every single slide */
scroll-snap-stop: always;
background: #ffffff;
border-radius: 16px;
padding: 24px;
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.08);
}
Inspecting Snap Stop Behaviors in Safari Web Inspector
Follow these steps to evaluate and debug snap stop behaviors inside Safari:
- Open Safari on macOS, navigate to your paginated interface, and open Web Inspector via Option+Command+I.
- In the Elements DOM tree, select the container declared with
scroll-snap-type(e.g.,.pagination-container). - Click the small scroll-snap badge adjacent to the element in the DOM tree to display the active snap overlay on the page viewport.
- Select an individual slide element in the DOM tree (e.g.,
.pagination-card). In the Styles panel, verify thatscroll-snap-stop: alwaysis recognized without syntax warnings. - Perform high-velocity two-finger horizontal swipes over the trackpad. Confirm that Safari stops cleanly on the immediate subsequent card without skipping to distant items.
- In the Styles panel, toggle
scroll-snap-stoptonormalinteractively. Repeat the high-velocity swipe gesture; observe how the viewport skips past three or four slides, demonstrating the contrast in user experience.
Best Practices and Accessibility Considerations
- Pair with Clear Visual Controls: While
scroll-snap-stop: alwaysguarantees single-card steps for touch and trackpad users, always provide explicit Next and Previous buttons for keyboard and screen reader accessibility. - Mobile Viewport Widths: Ensure slide widths account for container padding (e.g.,
calc(100% - 32px)) so neighboring cards offer visual affordance that additional content exists off-screen. - Avoid Trapping Users in Long Feeds: Do not apply
scroll-snap-stop: alwaysto long vertical newsfeeds or catalog search results, as requiring users to flick individually through hundreds of items causes extreme interaction fatigue.