Skip to main content

useScrollLock

useScrollLock is a hook that pauses scrolling on a page or container, such as keeping background content still behind an overlay.

Import

import { useScrollLock } from '@vortexlabs/vortex';

Other links

Page scrolling: Unlocked
"use client";

import { Button, Stack, Text, useScrollLock } from "@vortexlabs/vortex";
import React from "react";

export const UseScrollLockExample = () => {
  const { lockScroll, unlockScroll, scrollLocked } = useScrollLock();
  return (
    <Stack gap={3} align="start">
      <Text attributes={{ role: "status" }}>
        Page scrolling: {scrollLocked ? "Locked" : "Unlocked"}
      </Text>
      <Stack direction="row" gap={3} wrap>
        <Button disabled={scrollLocked} onClick={lockScroll}>
          Lock page scroll
        </Button>
        <Button variant="secondary" disabled={!scrollLocked} onClick={unlockScroll}>
          Unlock page scroll
        </Button>
      </Stack>
    </Stack>
  );
};

Features

  • Locks the document or a specific scrolling container.
  • Shares a target safely between multiple lock owners.
  • Releases its lock when the hook unmounts.

Call lockScroll() to lock and unlockScroll() to release this instance’s lock. scrollLocked reports this instance’s ownership, not every lock on the page.

Container Scrolling

Pass containerRef to lock a particular viewport instead of the document. Keep the unlock control outside that viewport so it stays available.

Activity entry 1
Activity entry 2
Activity entry 3
Activity entry 4
Activity entry 5
Activity entry 6
Activity entry 7
Activity entry 8
Activity entry 9
Activity entry 10
Activity entry 11
Activity entry 12
"use client";

import { Button, Stack, Text, useScrollLock } from "@vortexlabs/vortex";
import React from "react";

export const UseScrollLockContainerExample = () => {
  const containerRef = React.useRef<HTMLDivElement>(null);
  const { lockScroll, unlockScroll, scrollLocked } = useScrollLock({ containerRef });
  return (
    <Stack gap={3} width="100%">
      <Button variant="secondary" onClick={scrollLocked ? unlockScroll : lockScroll}>
        {scrollLocked ? "Unlock activity" : "Lock activity"}
      </Button>
      <div
        ref={containerRef}
        role="region"
        aria-label="Activity"
        tabIndex={0}
        style={{ height: "10rem", overflow: "auto" }}
      >
        <Stack gap={3} padding={3}>
          {Array.from({ length: 12 }, (_, index) => (
            <Text key={index}>Activity entry {index + 1}</Text>
          ))}
        </Stack>
      </div>
    </Stack>
  );
};

State and Cleanup

For a lock tied to your open state, acquire it in an effect and release it in cleanup.

const { lockScroll, unlockScroll } = useScrollLock();

React.useEffect(() => {
  if (!open) return;
  lockScroll();
  return unlockScroll;
}, [open, lockScroll, unlockScroll]);

Repeated calls from one hook do not add locks. The target stays locked until every owner releases it.

Target Options

Without refs, the target is the document body. Use originRef to find an element’s scrollable ancestor; containerRef takes precedence.

const { lockScroll, unlockScroll } = useScrollLock({
  originRef,
  compensateScrollbar: false,
});

Scrollbar compensation defaults to true. The first owner sets that policy for a shared lock.

Use Drawer for an overlay with focus handling and dismissal. This hook manages scrolling only.