Library / SDK
taion/scroll-behavior avatar
taion/scroll-behavior

scroll-behavior is the scroll layer a router integration plugs into

Pluggable browser scroll management

543 stars58 forksJavaScriptMIT

At a glance

What is it?
taion/scroll-behavior is a small JavaScript library that reproduces browser scroll behaviour for client side routing, and the README says outright that applications are not meant to use it directly. It exists to be embedded in routing integrations such as Found Scroll and react-router-scroll, and it takes its navigation events, its storage and its locations from the host framework.
Who is it for?
scroll-behavior is the right building block if you are writing or maintaining a router integration and need scroll restoration that matches what the browser does natively, including per-route rules and scrollable elements other than window. It is the wrong dependency for an application that just wants the back button to restore a scroll position, where the browser's own scroll restoration already does the job.
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 1 day ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A library that tells you not to use it directly

scroll-behavior is described as pluggable browser scroll management, and the very first paragraph of the README sets the ground rules. This library is not generally meant to be used directly by applications, and instead it is meant to be used in integrations for routing libraries or frameworks. Two such integrations are named, Found Scroll for Found and react-router-scroll for React Router v2 and v3. That framing is the whole design brief, and it explains several choices that would otherwise look odd. The library does not listen to the browser history itself, it does not own the router, and it does not keep a global store of scroll positions. It asks the host for three things and then does the work. The audience is therefore a small and specific one, the person writing a router integration, or the maintainer of a framework that ships one, and the value of the library is that it encodes the fiddly details of scroll restoration once instead of in every integration.

Three constructor arguments and one method to call

Creating a ScrollBehavior object takes three required arguments and one optional callback. The first is addNavigationListener, a function that takes a navigation listener function and returns an unlisten function, where the listener is called immediately before navigation updates the page and the unlisten function removes it when called. The second is stateStorage, an object implementing read and save methods, where save takes a location object, a nullable element key and a truthy value and saves it for the duration of the page session, and read takes a location object and a nullable element key and returns the value save was called with for that location and element key, or a falsy value if nothing is available. The third is getCurrentLocation, a function returning the current location object. The object then keeps track of the scroll position, and you call updateScroll on it after navigation to emulate the default browser scroll behaviour on page changes. The usage example is short:

js
import ScrollBehavior from 'scroll-behavior';

const scrollBehavior = new ScrollBehavior({
  addNavigationListener,
  stateStorage,
  getCurrentLocation,
  /* shouldUpdateScroll, */
});

// After navigation:
scrollBehavior.updateScroll(/* prevContext, context */);

Teardown is the stop method, which removes all listeners.

shouldUpdateScroll returns one of four things

The customisation point is the shouldUpdateScroll callback, passed when constructing the ScrollBehavior object, and it is where the interesting decisions live. When you call updateScroll you can pass up to two additional context arguments, and those are handed to the callback. The callback may return a falsy value, which suppresses updating the scroll position, a position array of x and y such as [0, 100] to scroll to that position, a string with the id or name of an element to scroll to that element, or a truthy value to emulate the browser default scroll behaviour. The README gives two worked examples, and the difference between them is the point. The first refuses to scroll when the pathname is unchanged:

js
const scrollBehavior = new ScrollBehavior({
  ...options,
  shouldUpdateScroll: (prevLocation, location) =>
    // Don't scroll if the pathname is the same.
    !prevLocation || location.pathname !== prevLocation.pathname,
});

The second scrolls to the top when you visit the path you are already on, which is a common complaint about single page apps:

js
const scrollBehavior = new ScrollBehavior({
  ...options,
  shouldUpdateScroll: (prevLocation, location) =>
    // Scroll to top when attempting to visit the current path.
    prevLocation && location.pathname === prevLocation.pathname
      ? [0, 0]
      : true,
});

The callback receives the previous and current location objects, so the decision is made per navigation rather than globally.

Scroll containers other than window need registration

Scrolling in a single page app is rarely confined to the window, because a virtualised list, a modal panel or a scrolling sidebar has its own scroll position that the browser knows nothing about. The library handles that with the registerElement method, which registers an element other than window to have managed scroll behaviour. Each element needs a unique key at registration time, and it can be given an optional shouldUpdateScroll callback that behaves as it does on the main object. The method signature is short:

js
scrollBehavior.registerScrollElement(
  key,
  element,
  shouldUpdateScroll,
  context,
);

The registerElement call should also be made with the current context per updateScroll, if applicable, to set up the element's initial scroll position. Removal is symmetric, through the unregisterElement method with the key used at registration. The design here is sensible rather than clever. Because the state is keyed by location and element key, a host that has several scrollable regions can keep their positions independently, and because the key comes from the host, the library never has to guess which element is which between route changes.

Subclassing is the escape hatch for smooth scrolling

When the callback options are not enough, the README's answer is to subclass. If you need to further customise scrolling behaviour, subclass the ScrollBehavior class and override methods as needed, and the example given is overriding scrollToTarget to use smooth scrolling for window, with the appropriate polyfill. The example shows the shape of the override, including the branch that leaves non-window elements to the base class and the branch that resolves a string target to an element before falling back to scrolling to the top when the fragment does not exist:

js
class SmoothScrollBehavior extends ScrollBehavior {
  scrollToTarget(element, target) {
    if (element !== window) {
      super.scrollToTarget(element, target);
      return;
    }

    if (typeof target === 'string') {
      const targetElement =
        document.getElementById(target) ||
        document.getElementsByName(target)[0];
      if (targetElement) {
        targetElement.scrollIntoView({ behavior: 'smooth' });
        return;
      }

The remaining lines of that example close the class, and one detail in the middle is worth noting, because it is a real edge case rather than a detail. When the target fragment does not exist in the document, the fallback is to scroll to the top, since scrolling to a missing anchor has no meaningful position. Integrations are asked to accept a createScrollBehavior callback so they can create an instance of a custom scroll behaviour class, which keeps the subclass out of the integration and lets the application choose the behaviour at the point where the integration is configured.

The package shape: babel builds, karma tests, dtslint types

The repository is a small, well-organised JavaScript package, and package.json shows how it ships. The name is scroll-behavior at version 0.11.0, the published files array lists lib and es, and the entry points are main pointing at lib/index.js, module pointing at es/index.js and types pointing at lib/index.d.ts. The build script chains three steps, a CommonJS build with babel into lib, an ES module build with babel into es using an esm environment, and a types step that copies the declaration files from types/ into lib. The type definitions are hand written rather than generated, which is why the test script includes a dtslint run over the types directory. The test command runs lint, then the type tests, then the unit tests, and the unit tests run under karma with a cross-env NODE_ENV=test setup, and the repo carries a karma.conf.js at the root, which is a browser test runner rather than a Node one. The formatting rules are pinned in the same file, with a print width of 79, single quotes and trailing commas, enforced by eslint and prettier with lint-staged on a husky pre-commit hook. The runtime dependencies are only three, dom-helpers, invariant and page-lifecycle, and the repository also carries a travis configuration, a codecov configuration and a renovate configuration for dependency updates.

Maintenance, and the case for the browser doing it yourself

The repository is not archived and the last push was on 2026-10-20, but the published releases tell a different story about the distribution channel. The listed versions are v0.11.0 on 2020-03-30, v0.10.0 on 2020-03-11 and v0.9.12 on 2020-03-11, so the npm line has not moved in a long time even though the branch is still being worked on. That gap matters for anyone planning to depend on it, because what you install from the registry is the 2020 build, and anything newer lives only in the repository. The licence is MIT, so there are no obligations beyond attribution, and the source of truth for the code is the master branch rather than a tag. The real alternative, and the one most applications should consider, is to let the browser handle it. The browser has its own scroll restoration behaviour for history navigations, which restores a position on back and forward without any JavaScript. The difference in approach is that native restoration is automatic and coarse, it restores the position the browser recorded, and it has no concept of a route policy or a scrollable element other than the document, whereas scroll-behavior gives you a per-navigation decision function, a storage layer you control and registration for other elements. If the requirement is only that the back button returns the user to where they were, the browser already does it and a library is overhead. If the requirement is that a specific route always starts at the top, or that a virtualised list restores its own offset, that is what this library is for, and the maintenance question is whether you are willing to own a dependency whose last npm release is from 2020.

Editorial conclusion

scroll-behavior is the right building block if you are writing or maintaining a router integration and need scroll restoration that matches what the browser does natively, including per-route rules and scrollable elements other than window. It is the wrong dependency for an application that just wants the back button to restore a scroll position, where the browser's own scroll restoration already does the job. Before depending on it, check that the published npm version matches the code you need, since the latest listed release is v0.11.0 from 2020-03-30 while the repository itself was last pushed on 2026-10-20.

Frequently asked questions

How do I install and use scroll-behavior?

Install it with npm i -S scroll-behavior, create a ScrollBehavior object with addNavigationListener, stateStorage and getCurrentLocation, then call updateScroll on it after navigation. Call the stop method to tear down all listeners.

What can the shouldUpdateScroll callback return?

A falsy value to suppress the scroll update, a position array of x and y such as [0, 100], a string with the id or name of an element to scroll to, or a truthy value to emulate the browser default scroll behaviour.

Can scroll-behavior manage scrollable elements other than the window?

Yes. You call registerScrollElement with a unique key, the element, an optional shouldUpdateScroll callback and the current context, and you remove it later with unregisterElement using the same key.

Is scroll-behavior meant to be used directly by applications?

No. The README says the library is not generally meant to be used directly by applications, and points to integrations such as Found Scroll for Found and react-router-scroll for React Router v2 and v3 as the intended usage.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. taion/scroll-behavior on GitHub
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/taion-scroll-behavior.svg)](https://hysenlabs.com/projects/taion-scroll-behavior)

Was this useful?