useScrollLock
Lock scroll on document.body (or any element) without a layout jump.
Sets overflow: hidden and compensates for the scrollbar width.
Reference-counted per target - nested overlays locking the same element do not unlock prematurely.
Original styles are restored when the last lock releases or the component unmounts. SSR-safe.
Reactive (declarative)
Pass a reactive getter - the most common pattern. Scroll locks while the getter returns true and unlocks automatically when it becomes false.
Use the Modal component - it calls useScrollLock internally.
<script lang="ts">
import { Modal, Button } from 'sv5ui';
import { useScrollLock } from 'sv5ui';
let open = $state(false);
// Scroll locks while open is true, unlocks when false
useScrollLock(() => open);
</script>
<Button label="Open Modal" onclick={() => (open = true)} />
<Modal title="Scroll is locked" bind:open>
{#snippet children({ props })}
<Button {...props} label="Open Modal" onclick={() => (open = true)} />
{/snippet}
<p>The page scroll is locked while this modal is open.</p>
</Modal>Imperative control
Use the returned lock and unlock methods for programmatic control. Pass false as the initial value to start unlocked.
<script lang="ts">
import { Button } from 'sv5ui';
import { useScrollLock } from 'sv5ui';
// Pass false as initial state - control via lock/unlock
const scroll = useScrollLock(false);
</script>
<div class="flex gap-2">
<Button label="Lock scroll" onclick={() => scroll.lock()} />
<Button label="Unlock scroll" variant="outline" onclick={() => scroll.unlock()} />
</div>Custom target element
Pass a second argument to lock a specific scrollable element instead of document.body. Use a getter when the element is bound with bind:this.
Line 1
Line 2
Line 3
Line 4
Line 5
Line 6
Line 7
Line 8
Line 9
Line 10
Line 11
Line 12
Line 13
Line 14
Line 15
Line 16
Line 17
Line 18
Line 19
Line 20
<script lang="ts">
import { useScrollLock } from 'sv5ui';
let panel = $state<HTMLDivElement>();
let locked = $state(false);
// Lock a scrollable panel instead of document.body
useScrollLock(() => locked, () => panel);
</script>
<button onclick={() => (locked = !locked)}>
{locked ? 'Unlock panel' : 'Lock panel'}
</button>
<div bind:this={panel} class="h-40 overflow-y-auto rounded border p-2" style="overflow-y: scroll;">
{#each Array(20) as _, i}
<p>Line {i + 1}</p>
{/each}
</div>Parameters
| Parameter | Type | Default |
|---|---|---|
locked | boolean | (() => boolean) | - |
target | HTMLElement | (() => HTMLElement | null) | null | document.body |
Return
| Property | Type | - |
|---|---|---|
lock | () => void | - |
unlock | () => void | - |