Skip to content

useScrollLock

standalonescrollutility

Prevent body or element scrolling with reference counting. Multiple locks on the same element stack.

useScrollLock prevents an element from scrolling while it is enabled, then restores the previous overflow when released. Locks are reference-counted per element, so two overlays can lock document.body independently and scrolling is only restored once both release. It is standalone (no provider) and ships as a copy-paste utility hook.

tsx
useScrollLock({ enabled: open })

When to use it

  • Freeze background scroll behind a modal, drawer, sheet, or full-screen overlay.
  • Lock a specific scroll container (pass target) rather than the whole page.

Pair it with useFocusTrap to hold focus inside a modal while its background is locked.

Limitations

useScrollLock toggles overflow: hidden on the target element with refcounted restore. It also compensates for scrollbar width with padding-right and marks the locked element with data-scroll-locked. It does not implement the full react-remove-scroll / Radix useScrollLock parity surface:

  • Defaults to the host document.body. In multi-window or iframe setups, capture the trigger's owner document after mount and pass a ref to that document's body:

    tsx
    const triggerRef = useRef<HTMLButtonElement>(null);
    const bodyRef = useRef<HTMLElement | null>(null);
    
    useEffect(() => {
      bodyRef.current = triggerRef.current?.ownerDocument.body ?? null;
    });
    
    useScrollLock({ target: bodyRef, enabled: open });

    Mount order matters: register the effect before calling useScrollLock so the target ref is populated before the lock effect runs.

  • No iOS Safari rubber-band guard. Setting overflow: hidden on body does not stop iOS touch scrolling. The page can still pan-scroll on iOS while the lock is active.

iOS rubber-band compensation is not part of the current surface; use a dedicated touch-scroll guard when that behavior matters.

Installation

$pnpm exec dgadd add keys/scroll-lock
[Installs to]src/hooks/use-scroll-lock.ts[Item]keys/scroll-lock

dgadd is not public on npm yet. Until the first release, pack @diffgazer/add from the repository and install that tarball into this app, which is what puts dgadd on pnpm exec.

@diffgazer/keys requires no CSS or Tailwind setup.

Parameters

NameTypeDefaultDescription
targetRefObject<HTMLElement | null>Ref to the element to lock. Defaults to document.body when omitted.
enabledbooleantrueWhether the scroll lock is active.

Returns

voidThis hook does not return a value.

Examples

Lock body scroll in a modal

Preview

Lock a specific scroll container

Preview

Notes

Reference counting

Multiple useScrollLock calls on the same element increment a shared counter. Scrolling is only restored when all locks are released, preventing double-unlock bugs.

Standalone

useScrollLock does not require KeyboardProvider. It works independently as a copy-paste utility hook.

Edge cases

  • Toggling enabled. Flipping enabled to false releases this hook's lock immediately; the element stays locked while any other lock remains.

Source

Highlighted source loads after this disclosure opens. Browse the source repository.