useLockedBody
Prevent body scrolling when a modal or overlay is active.
Scroll Lock Demo
Click the button below to enter "Zen Mode". This will lock the body scroll.
Body Scroll: UNLOCKED
Usage
import { useLockedBody } from 'usehooks-ts'
import { useState } from 'react'
export default function Modal() {
const [locked, setLocked] = useLockedBody()
return (
<div>
<button onClick={() => setLocked(true)}>Open Modal</button>
{locked && (
<div className="modal-overlay">
<div className="modal-content">
<h1>Modal Open</h1>
<button onClick={() => setLocked(false)}>Close</button>
</div>
</div>
)}
</div>
)
}API
const [locked, setLocked] = useLockedBody(initialLocked?, rootId?)Parameters
| Name | Type | Default | Description |
|---|---|---|---|
initialLocked | boolean | false | Initial lock state |
rootId | string | 'root' | ID of the application root element (to handle scrollbar width compensation) |
Return Values
| Name | Type | Description |
|---|---|---|
locked | boolean | Current lock state |
setLocked | (locked: boolean) => void | Function to toggle lock state |
Hook
import { useEffect, useState } from 'react'
const useIsomorphicLayoutEffect =
typeof window !== 'undefined' ? useEffect : useEffect
export function useLockedBody(initialLocked = false, rootId = 'root') {
const [locked, setLocked] = useState(initialLocked)
// Do the locking
useIsomorphicLayoutEffect(() => {
if (!locked) return
// Save initial body style
const originalOverflow = document.body.style.overflow
const originalPaddingRight = document.body.style.paddingRight
// Lock body scroll
document.body.style.overflow = 'hidden'
// Get the scrollBar width
const root = document.getElementById(rootId) // or root
const scrollBarWidth = root ? root.offsetWidth - root.clientWidth : 0
// Avoid width reflow
if (scrollBarWidth) {
document.body.style.paddingRight = `${scrollBarWidth}px`
}
return () => {
document.body.style.overflow = originalOverflow
if (scrollBarWidth) {
document.body.style.paddingRight = originalPaddingRight
}
}
}, [locked, rootId])
// Update state if initialLocked changes
useEffect(() => {
if (locked !== initialLocked) {
setLocked(initialLocked)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [initialLocked])
return [locked, setLocked] as const
}