Scroll lock
import { lockScroll, isScrollLocked } from "surface-elements/scroll-lock"
const release = lockScroll()// …release()lockScroll()
Section titled “lockScroll()”Sets overflow: hidden on <html> and returns a release function.
- Ref-counted. The page unlocks when every caller has released. Stacked drawers each hold their own lock.
- One count per page. The count lives on
Symbol.for("surface-elements.scroll-lock"), so copy files and the package share it when they run on the same page. - Idempotent release. Calling a release twice is harmless.
- No layout shift. With a classic scrollbar it sets
scrollbar-gutter: stableso the page does not jump sideways. - Restores what it found. Inline
overflowandscrollbar-guttervalues you had set come back on unlock.
function lockScroll(): () => voidisScrollLocked()
Section titled “isScrollLocked()”Whether any lock is held.
function isScrollLocked(): booleanWhy root overflow
Section titled “Why root overflow”Platform guidance prefers overscroll-behavior: contain on the dialog and its backdrop. Safari ignores it on containers that do not scroll, so the page behind still scrolls there. The library keeps the CSS containment and adds this lock on top. The trade-offs are in ADR 0002.