All hooks / Browser APIs

useScrollLock

270 B minified and brotliedSourceMarkdown✓ Works in all modern browsers

Stops the page from scrolling while locked is true, e.g. behind a modal.

Sets overflow: hidden on <body> and pads it by the scrollbar width so the layout does not shift. Locks are counted, so nested modals work: the page unlocks when the last one releases. Restores the original styles. Applied in a layout effect, so there is no frame where the page can scroll.

Import

import { useScrollLock } from '@vincent.ri/react-tils'
// or only this hook
import { useScrollLock } from '@vincent.ri/react-tils/useScrollLock'

Usage

function Modal({ open, children }: { open: boolean; children: React.ReactNode }) {
  useScrollLock(open)
  return open ? <div role="dialog" aria-modal="true">{children}</div> : null
}

Live demo

Opens a modal; the page can't scroll behind it.

When to use it

A modal, drawer, or full-screen menu is open.

When not to

You use the native <dialog> with showModal(), where CSS body:has(dialog[open]) { overflow: hidden } is enough. To lock scrolling inside an element, use CSS overscroll-behavior: contain.

API

function useScrollLock(locked?: boolean): void
ParameterDescription
lockedLock while true. Default true.

Browser support

Uses only long-established browser features, so it works wherever React does.

First version with support, from MDN's compatibility data (web-features 3.42.0, @mdn/browser-compat-data 8.1.5).

Coming from another library?

Other libraries call this useLockBodyScroll.