npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

scroll-utils

v1.0.1

Published

A super lightweight scroll utility library built for React applications.

Readme

Scroll Utils

A lightweight, dependency-free set of scroll utilities for React and vanilla web apps — positional scrolling, scroll-to-element, SSR-safe helpers, and a ref-counted scroll-locking mechanism, all in a ~1.7 KB bundle.

License: Apache-2.0 Bundle Size TypeScript

Table of Contents

Features

  • Tiny — ~1.7 KB minified, zero runtime dependencies
  • Positional scrolling — scroll to top, bottom, left, or right with optional offsets
  • Scroll-to-element — scroll to any element by ID, with automatic focus management for accessibility
  • React-friendly — handler factories built for onClick / event-based usage
  • Reduced-motion aware — automatically switches to instant scrolling when the user prefers reduced motion
  • Scroll locking — ref-counted, queue-based scroll lock/unlock with automatic scrollbar-gutter compensation and an iOS Safari touchmove fix
  • SSR-safe — every function no-ops gracefully when window is undefined
  • Fully typed — ships with complete TypeScript definitions

Installation

npm install scroll-utils
yarn add scroll-utils
pnpm add scroll-utils

Getting Started

import { scrollToTop, scrollToId } from 'scroll-utils';

// Scroll the window to the top
scrollToTop();

// Scroll to an element by ID
scrollToId({ id: 'section-2', behavior: 'smooth' });

API Reference

scroll(settings: ScrollSettings): void

The core positional scroll function. All directional helpers below are thin wrappers around this.

| Param | Type | Default | Description | | -------------------- | -------------------------------- | --------------------- | ------------------------------------------------------------- | | settings.position | ScrollPosition | 'top' | Direction to scroll: 'top', 'bottom', 'left', 'right' | | settings.offset | ScrollOffset | { top: 0, left: 0 } | Extra offset applied to the computed scroll position | | settings.container | HTMLElement \| Window | window | Element to scroll instead of the window | | settings.behavior | ScrollBehavior | 'smooth' | 'auto', 'smooth', or 'instant' | | settings.event | React.MouseEvent \| MouseEvent | — | If provided, preventDefault() is called on it |

scroll({
  position: 'right',
  offset: { top: 100, left: 50 },
  container: document.getElementById('scrollable-container'),
  behavior: 'smooth',
});

If the user's OS has "reduce motion" enabled, behavior is automatically overridden to 'instant'.

scrollToTop / scrollToBottom / scrollToLeft / scrollToRight

Convenience wrappers around scroll() that lock in the position for you. Each accepts the same settings minus position.

scrollToTop({ offset: { top: 10 } });
scrollToBottom({ container: myContainerEl });
scrollToLeft();
scrollToRight({ behavior: 'auto' });

scrollToId(settings: ScrollToIdSettings): void

Scrolls to a specific element by its ID (with or without a leading #). When scrolling within a custom container, the target element is also focused (with tabindex="-1" applied if needed) for keyboard/screen-reader accessibility.

| Param | Type | Default | Description | | -------------------- | -------------------------------- | --------------------- | --------------------------------------------------------------- | | settings.id | string | '' | The target element's ID, e.g. 'my-section' or '#my-section' | | settings.offset | ScrollOffset | { top: 0, left: 0 } | Extra offset applied to the computed scroll position | | settings.container | HTMLElement \| Window | window | Element to scroll instead of the window | | settings.behavior | ScrollBehavior | 'smooth' | 'auto', 'smooth', or 'instant' | | settings.event | React.MouseEvent \| MouseEvent | — | If provided, preventDefault() is called on it |

scrollToId({
  id: 'pricing',
  offset: { top: 20, left: 0 },
  behavior: 'smooth',
});

If the element isn't found, a console.warn is emitted and the function returns without throwing.

createScrollHandler(settings: ScrollSettings)

Returns a ready-to-use event handler for onClick (or any DOM event) that triggers scroll() with the given settings.

const handleScrollTop = createScrollHandler({ position: 'top' });

<button onClick={handleScrollTop}>Back to top</button>

createScrollToIdHandler(settings: ScrollToIdSettings)

Same idea as createScrollHandler, but for scrollToId().

const handleScrollToPricing = createScrollToIdHandler({ id: 'pricing' });

<button onClick={handleScrollToPricing}>View pricing</button>

lockScroll / unlockScroll

lockScroll(container?: HTMLElement): Promise<void>
unlockScroll(container?: HTMLElement): Promise<void>

Lock or unlock scrolling on a given container (defaults to document.documentElement, i.e. the whole page). Calls are queued internally, so rapid lock/unlock calls always execute in order and never race each other.

import { lockScroll, unlockScroll } from 'scroll-utils';

// Lock the whole page (e.g. when opening a modal)
await lockScroll();

// ...later, when the modal closes
await unlockScroll();

// Or lock a specific scrollable container
const drawer = document.getElementById('drawer');
await lockScroll(drawer);
await unlockScroll(drawer);

Locking is ref-counted. If lockScroll() is called twice on the same container (e.g. two modals stacked), the container only unlocks once unlockScroll() has also been called twice — matching lock/unlock calls one-to-one.

What locking does under the hood:

  • Sets overflow: hidden on the container
  • Compensates for scrollbar removal by adjusting padding-right (skipped if scrollbar-gutter: stable is already set)
  • For the document element specifically, pins it with position: fixed and restores the exact scroll offset on unlock
  • Adds a touchmove listener to prevent iOS Safari's rubber-band scroll-through
  • Sets a data-scroll-locked="true" attribute on the locked element (handy for CSS hooks)
  • Restores all original inline styles on unlock

Type Reference

type ScrollPosition = 'top' | 'bottom' | 'left' | 'right';

type ScrollBehavior = 'auto' | 'smooth' | 'instant';

interface ScrollOffset {
  top: number;
  left: number;
}

interface ScrollSettings {
  position: ScrollPosition;
  offset?: ScrollOffset;
  container?: HTMLElement | Window;
  behavior?: ScrollBehavior;
  event?: React.MouseEvent | MouseEvent;
}

interface ScrollToIdSettings {
  id: string;
  offset?: ScrollOffset;
  container?: HTMLElement | Window;
  behavior?: ScrollBehavior;
  event?: React.MouseEvent | MouseEvent;
}

interface ScrollLockMetadata {
  count: number;
  scrollTop: number;
  originalStyle: {
    overflow: string;
    position: string;
    top: string;
    width: string;
    paddingRight: string;
  };
}

All types are exported from the package root and can be imported directly:

import type { ScrollSettings, ScrollToIdSettings } from 'scroll-utils';

React Usage Examples

Scroll-to-top button:

import { createScrollHandler } from 'scroll-utils';

function BackToTopButton() {
  return (
    <button onClick={createScrollHandler({ position: 'top', behavior: 'smooth' })}>
      ↑ Back to top
    </button>
  );
}

Anchor-style navigation:

import { createScrollToIdHandler } from 'scroll-utils';

function NavLink({ id, label }: { id: string; label: string }) {
  return (
    <a href={`#${id}`} onClick={createScrollToIdHandler({ id, offset: { top: 64, left: 0 } })}>
      {label}
    </a>
  );
}

Modal with scroll lock:

import { useEffect } from 'react';
import { lockScroll, unlockScroll } from 'scroll-utils';

function Modal({ isOpen, children }: { isOpen: boolean; children: React.ReactNode }) {
  useEffect(() => {
    if (!isOpen) return;

    lockScroll();
    return () => {
      unlockScroll();
    };
  }, [isOpen]);

  if (!isOpen) return null;
  return <div className="modal">{children}</div>;
}

SSR / Next.js Support

Every function checks for window before touching the DOM and safely no-ops during server-side rendering — no extra guards needed in your components. isWindowUndefined() is used internally for this check.

Accessibility

  • scroll() and scrollToId() respect prefers-reduced-motion and automatically fall back to instant scrolling.
  • scrollToId() moves focus to the target element when scrolling within a custom container, so keyboard and screen-reader users land in the right place.
  • Scroll locking preserves and restores the original scroll position and styles exactly, avoiding layout shift when a modal or drawer closes.

License

Apache-2.0 © 2026-present Suryansh Singh