← Back to all hooks

useScrollLock

Prevent body scroll when modals or overlays are open. Prevents layout shift by reserving scrollbar space.

Live Demo

💡 Try this: Scroll down this page, then open the modal. Notice how the body scroll is locked while the modal is open!

Scroll down to test the scroll lock feature

This content makes the page scrollable. Open the modal to see scroll locking in action.

Code Examples

Basic Modal with Scroll Lock

import { useScrollLock } from "@uiblock/hooks";
import { useState } from "react";

function Modal({ isOpen, onClose, children }) {
  useScrollLock({ enabled: isOpen });

  if (!isOpen) return null;

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-content" onClick={e => e.stopPropagation()}>
        {children}
        <button onClick={onClose}>Close</button>
      </div>
    </div>
  );
}

With Options

import { useScrollLock } from "@uiblock/hooks";

function Drawer({ isOpen }) {
  useScrollLock({
    enabled: isOpen,
    reserveScrollBarGap: true, // Prevent layout shift
    allowTouchMove: false // Prevent touch scrolling
  });

  return isOpen ? <div>Drawer content</div> : null;
}

How It Works

Here's the implementation:

import { useEffect } from 'react'

export function useScrollLock(options = {}) {
  const {
    enabled = true,
    reserveScrollBarGap = true,
    allowTouchMove = false
  } = options

  useEffect(() => {
    if (!enabled) return

    const originalStyle = {
      overflow: document.body.style.overflow,
      paddingRight: document.body.style.paddingRight
    }

    // Calculate scrollbar width
    const scrollBarWidth = window.innerWidth - document.documentElement.clientWidth

    // Lock scroll
    document.body.style.overflow = 'hidden'

    // Reserve scrollbar space to prevent layout shift
    if (reserveScrollBarGap && scrollBarWidth > 0) {
      document.body.style.paddingRight = `${scrollBarWidth}px`
    }

    // Prevent touch move if not allowed
    const preventTouchMove = (e) => {
      if (!allowTouchMove) {
        e.preventDefault()
      }
    }

    if (!allowTouchMove) {
      document.addEventListener('touchmove', preventTouchMove, {
        passive: false
      })
    }

    // Cleanup
    return () => {
      document.body.style.overflow = originalStyle.overflow
      document.body.style.paddingRight = originalStyle.paddingRight

      if (!allowTouchMove) {
        document.removeEventListener('touchmove', preventTouchMove)
      }
    }
  }, [enabled, reserveScrollBarGap, allowTouchMove])
}

Key Features:

  • Prevents body scroll when enabled
  • Reserves scrollbar space to prevent layout shift
  • Optionally prevents touch scrolling
  • Automatic cleanup on unmount

API Reference

Options

enabled?: boolean

Whether to lock the scroll (default: true)

reserveScrollBarGap?: boolean

Reserve space for scrollbar to prevent layout shift (default: true)

allowTouchMove?: boolean

Allow touch move events (default: false)

💡 Use Cases

  • Modal dialogs
  • Side drawers
  • Full-screen overlays
  • Mobile menus
  • Lightboxes