Skip to content
usefy

useScrollLock

Layout & observers

Lock the page scroll for modals, drawers, and menus — iOS-aware, nested-lock counted, with scroll-position restore

Install

$ npm install @usefy/use-scroll-lock

Quick start

useScrollLock.tsx
import { useEffect, useState } from "react";
import { useScrollLock } from "@usefy/use-scroll-lock";

function Modal() {
  const [open, setOpen] = useState(false);
  const { lock, unlock } = useScrollLock();

  useEffect(() => {
    if (open) lock();
    else unlock();
  }, [open, lock, unlock]);

  return (
    <>
      <button onClick={() => setOpen(true)}>Open modal</button>
      {open && (
        <div role="dialog" aria-modal="true">
          <button onClick={() => setOpen(false)}>Close</button>
        </div>
      )}
    </>
  );
}

API reference

useScrollLock(options?)

Returns { lock, unlock, isLocked } for locking the page scroll.

Options — UseScrollLockOptions

OptionTypeDefaultDescription
enabledbooleanfalseWhen true, hold a lock automatically for as long as it stays true (locks on mount, releases on unmount / when it flips false). Let it own the lock — don't also call lock/unlock on the same instance

Returns — UseScrollLockReturn

PropertyTypeDescription
lock() => voidAcquire a lock for this instance. Stable (useCallback) and idempotent — never double-counts against the shared counter
unlock() => voidRelease this instance's lock. Stable and idempotent. The body is only restored once the last lock across all instances is gone
isLockedbooleanWhether this instance currently holds a lock (the body may still be locked by another instance)

Also exported: the isIOS and getScrollbarWidth helpers, and the UseScrollLockOptions / UseScrollLockReturn types.

Go deeper

This page is the quick reference. For every example, prop, and edge case, read the full README — or open Storybook to change props live.

More in layout & observers