scroll-snap snaps the page after the scroll stops, not while it moves
↯ Snap page when user stops scrolling, with a customizable configuration and a consistent cross browser behaviour
At a glance
- What is it?
- scroll-snap is a small TypeScript library that waits for scrolling to end, then animates the container to a snap point you configure, with a threshold, a duration and your own easing curve. It suits designers who want page snapping behaviour that matches across browsers rather than inheriting each engine's defaults.
- Who is it for?
- Adopt scroll-snap when you need full page snapping with a custom threshold, easing curve and cross browser consistency, and when the container is a plain scrollable element you control. Do not adopt it for long article pages where free scrolling is the point, since a snap destination of 100% will hijack every scroll gesture.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 11 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A JavaScript take on CSS Scroll Snap
The project describes itself as a TypeScript library providing smooth and customizable page snapping, built as a modern implementation of CSS Scroll Snap. The distinction between those two things is the whole point of the library, and it is worth being precise about it. The CSS feature is a property on a scroll container, and the browser decides when and how hard to snap, with the timing and the feel of that snap outside your control. This library takes the decision into JavaScript. You give it a scrollable element and a set of options, it listens for scrolling, and once the scrolling stops it animates the container to the position you specified. The README lists cross-browser support, zero dependencies, TypeScript readiness and a size of roughly 2KB gzipped.
The snap fires after the scroll ends, on a timeout
The mechanism explains most of the behaviour you will notice. The library has a `timeout` option, documented as the delay before snapping after scroll ends, with a default of 100 milliseconds, and a separate `duration` option for the animation itself, defaulting to 300 milliseconds. Two timings rather than one is what lets a page settle briefly and then glide. The `threshold` option, defaulting to 0.2, is the scroll distance threshold expressed as a value between 0 and 1, and it decides whether a given movement is far enough to be treated as a page change rather than a nudge. Set it too low and the page jumps during ordinary reading gestures. Set it too high and a deliberate page advance does nothing.
Snap destinations, easing and the snapStop rule
The destinations are given as strings or numbers, with `snapDestinationX` for horizontal snap points such as `100%` or `500px` and `snapDestinationY` for the vertical axis with the same kinds of values. There is also a `snapStop` option, defaulting to false, which prevents skipping intermediate snap positions. That single flag is the difference between a carousel where a fast swipe moves one page and one where it flies past three. The easing option takes a function of a number and defaults to linear, so the default animation is a constant speed move. The README's vertical example shows a custom curve, `t => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t`, which is an ease in and out curve you can substitute with anything of the same shape.
Keyboard arrows and optional hover navigation arrows
Navigation is part of the feature set rather than something you build. `enableKeyboard` defaults to true and enables arrow key navigation, so a reader can move between snapped pages with the keyboard without any extra code from you. `showArrows`, defaulting to false, shows navigation arrows when hovering, which is the affordance a carousel usually needs on desktop. Both are opt-in or opt-out at configuration time, which matters if your design system already draws its own controls. The README does not describe what the arrows look like, where they are positioned, or whether they can be styled, so treat that as a default rather than a component you can reshape.
bind() and unbind() are the entire lifecycle
The API surface is two methods, and the README describes them exactly. `bind()` enables scroll snapping and attaches event listeners. `unbind()` disables scroll snapping and removes event listeners. Nothing in between is documented, so there is no documented way to change a snap destination after creation, no event callback and no per-snap hook. The constructor returns an object with those two functions, which means the usual pattern is to create the instance once, call `bind()` when you want snapping to be live, and call `unbind()` when a route change, a modal opening or a re-render makes the old listeners wrong. For a single page application this lifecycle matters, because a container that is torn down and rebuilt usually needs its listener removed first.
Installing the package and creating your first instance
The package is published on npm and the README's install step uses yarn:
yarn add scroll-snapThe basic usage creates the instance against a DOM element and passes the snap destination:
import createScrollSnap from 'scroll-snap'
const element = document.getElementById('container')
const { bind, unbind } = createScrollSnap(element, {
snapDestinationY: '100%',
})A horizontal gallery is the same call with the X option and the navigation flags turned on:
const scrollSnap = createScrollSnap(element, {
snapDestinationX: '100%',
showArrows: true,
enableKeyboard: true
})For React, the README does not describe a hook of its own but points to a React Hooks demo on CodeSandbox, so the integration story there is example code rather than a shipped API. The published package ships several builds: a CommonJS entry at `dist/scroll-snap.min.js`, an ES module at `dist/scroll-snap.esm.js`, the unpkg and jsDelivr targets, and TypeScript declarations at `dist/index.d.ts`.
The playground, the test stacks and the build pipeline
The repository is set up for hacking on the library itself. Cloning and installing is the usual yarn flow:
git clone https://github.com/lucafalasco/scroll-snap.git
cd scroll-snap
# Install dependencies
yarn
# Start development server
yarn start
# Run unit tests
yarn test
# Run e2e tests
yarn test:e2e
# Build for production
yarn buildThe dev server starts at `http://localhost:8080` with hot reloading and a playground environment for testing. The test stack is visible in the top level files: Jest with a jsdom environment and the testing library, plus Playwright for the end to end suite, driven by `playwright.config.ts`. The build itself is a hybrid, with both a Vite config and a Webpack config, and the package scripts include a `build:clean` step that clears `dist/` before the bundles are produced. Two type checking configs exist, one for the build and one for the published declarations.
MIT licensed, with a wide browser floor and uneven release history
The licence is MIT, held by Luca Falasco, which in short terms means you can use, modify and redistribute it, with the notice kept intact. The stated browser support is Chrome 61 and later, Firefox 63 and later, Safari 11 and later, and Edge 79 and later. The release history is worth noticing. The tagged versions are v5.0.0 on 2025-01-31, v4.0.3 on 2022-03-08 and v4.0.0 on 2021-12-10, so there is a long gap between v4 and v5, while the `package.json` in the repository carries version 5.0.2 and the last push was on 2026-10-14. The repository is not archived. In other words, the code is moving, but the published tags do not tell you precisely which commit you are installing, so pinning by tag means pinning to a release from early 2025.
Editorial conclusion
Adopt scroll-snap when you need full page snapping with a custom threshold, easing curve and cross browser consistency, and when the container is a plain scrollable element you control. Do not adopt it for long article pages where free scrolling is the point, since a snap destination of 100% will hijack every scroll gesture. Verify first that your scroll container is the element you pass to `createScrollSnap`, and remember the binding is manual: nothing moves until you call `bind()`.
Frequently asked questions
How do I install scroll-snap and snap a container to full pages?
Install it with `yarn add scroll-snap`, then call `createScrollSnap(element, { snapDestinationY: '100%' })` on the scrollable element. Destinations accept strings such as `100%` or `500px`.
What does the threshold option do in scroll-snap?
It is the scroll distance threshold, expressed as a value between 0 and 1 with a default of 0.2, that decides how far the user must scroll before a snap is triggered.
How do I stop scroll-snap from listening to scroll events?
The API exposes `bind()`, which enables snapping and attaches the listeners, and `unbind()`, which disables snapping and removes them. Call `unbind()` when the container is replaced.
Can scroll-snap animate with a custom easing curve?
Yes. The `easing` option takes a function of a number and defaults to linear, and the README shows a vertical example with an ease in and out curve.
Which browsers does scroll-snap support?
The README lists Chrome 61 and later, Firefox 63 and later, Safari 11 and later, and Edge 79 and later. The library is also described as cross-browser with zero dependencies and about 2KB gzipped.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/lucafalasco-scroll-snap)Was this useful?