Manual UI Behaviors lockScroll

lockScroll

elements man ui/behaviors/scroll-lock

Stops the page scrolling under a modal. Locks nest: the page unlocks when the last lock is released, so a dialog opened from a dialog is safe. The lock sets overflow: hidden on <html>, so it captures no events: every scroller inside the modal keeps scrolling natively, pinch zoom still works, and the scrollbar's width is added to the body's right padding so content does not shift when the scrollbar disappears.

Signatures
function lockScroll(opts?: ScrollLockOptions): () => void; function scrollLockCount(): number;

lockScroll returns the unlock function; calling it twice is harmless.

Options

Option Type Default What it does
removeScrollBar boolean true Hide the page's scrollbar (overflow: hidden, gap compensated). false keeps it on screen and blocks wheel, touch and key scrolling of the page instead.

With the scrollbar removed, its width is published as --scrollbar-gap on <html> for fixed elements that need the same compensation:

app/shared/styles/page.css
.site-header { padding-right: calc(var(--space-4) + var(--scrollbar-gap, 0px)); }

A root with scrollbar-gutter: stable keeps its gutter, so no padding is added and --scrollbar-gap is 0px.

With removeScrollBar: false the page does not scroll by wheel, touch or keys, while a scroller that can still move in that direction takes the gesture as usual. A scroll key is left alone only when the focused element itself uses it: a field, a slider's arrows, a button's Space. A pinch (Ctrl+wheel, two fingers) is never blocked. When any held lock removes the bar, the page uses overflow: hidden as above.

In a template of your own

app/shared/templates/overlay/index.ehtml
import { lockScroll } from "@elements/ui/behaviors"; interface OverlayState { unlock?: () => void; } <Overlay (open: boolean = false, private ui: OverlayState = {})> <div e:if={open} class="overlay" oninsert={() => ui.unlock = lockScroll()} onremove={() => ui.unlock?.()}> <slot/> </div> </Overlay>

Writes

data-scroll-locked on <body> while any lock is held. With the scrollbar removed: overflow: hidden on <html>, the scrollbar width added to the body's padding-right, and --scrollbar-gap on <html>. With removeScrollBar: false, none of those: wheel, touch and key listeners on the document instead.

Used by

Combobox (opens in a new tab), Dialog (opens in a new tab), DropdownMenu (opens in a new tab), ContextMenu (opens in a new tab), Menubar (opens in a new tab), Popover (opens in a new tab), Select (opens in a new tab).