Reactive primitives to react to element/window scrolling, and to prevent scroll outside of a given element.
| Stage | Category | Version | Last Updated | Demo |
|---|---|---|---|---|
| 3 | Inputs | 3.0.0-next.4 (next) | Aug 12, 2026 | Demo → |
npm i @solid-primitives/scroll@nextReactive primitives to react to element/window scrolling, and to prevent scroll outside of a given element.
createScrollPosition- Reactive primitive providing a store-like object with current scroll position of specified target.useWindowScrollPosition- Returns a reactive object with current window scroll position.createPreventScroll- Prevents scrolling outside of a given element.
createScrollPosition
Reactive primitive providing a store-like object with current scroll position of specified target.
How to use it
import { createScrollPosition } from "@solid-primitives/scroll";
// target will be window by defaultconst windowScroll = createScrollPosition();
createEffect(() => { // returned object is a reactive store-like structure windowScroll.x; // => number windowScroll.y; // => number});With element refs
let ref: HTMLDivElement | undefined;
// pass as function — preferred, handles ref population automaticallyconst scroll = createScrollPosition(() => ref);// or wrap with onSettledonSettled(() => { const scroll = createScrollPosition(ref!);});
<div ref={e => (ref = e)} />;Reactive Target
The element target can be a reactive signal.
const [target, setTarget] = createSignal<Element | undefined>(element);
const scroll = createScrollPosition(target);
// if target is undefined, scroll values will be 0scroll.x; // => numberscroll.y; // => number
// update the tracking elementsetTarget(ref);
// disable trackingsetTarget(undefined);Destructuring
If you are interested in listening to only single axis, you'd still have to access scroll.y as a property. To use it as a separate signal, you can wrap it with a function, or use destructure helper.
const scroll = createScrollPosition();const x = () => scroll.x;x(); // => number
// or destructure
import { destructure } from "@solid-primitives/destructure";const { x, y } = destructure(createScrollPosition());x(); // => numbery(); // => numberuseWindowScrollPosition
Returns a reactive object with current window scroll position.
useWindowScrollPosition is a singleton root primitive, hence the object instance, signals and event-listeners are shared between dependents, making it more optimized to use in multiple places at once.
const scroll = useWindowScrollPosition();
createEffect(() => { console.log( scroll.x, // => number scroll.y, // => number );});Additional helpers
getScrollPosition
Get an { x: number, y: number } object of element/window scroll position.
createPreventScroll
Prevents scrolling outside of the given element by intercepting wheel and touchmove events and optionally hiding the <body> scrollbar.
Adapted from solid-prevent-scroll by Jasmin Noetzli (GiyoMoon), part of the corvu project, which is itself inspired by react-remove-scroll by Anton Korzunov. Adapted for Solid 2.0 and solid-primitives conventions.
How to use it
import { createPreventScroll } from "@solid-primitives/scroll";
// Prevent all page scroll (no element specified)createPreventScroll();
// Prevent scroll outside a specific elementcreatePreventScroll({ element: () => myElement });
// Using a signal ref — preferred pattern for JSX refsconst [ref, setRef] = createSignal<HTMLElement>();createPreventScroll({ element: ref });
<div ref={setRef} />;
// Reactive enabled toggleconst [open, setOpen] = createSignal(false);createPreventScroll({ enabled: open });Props
| Prop | Type | Default | Description |
|---|---|---|---|
element | MaybeAccessor<HTMLElement | undefined> | undefined | Allow scroll inside this element. Events outside it are cancelled. |
enabled | MaybeAccessor<boolean> | true | Whether scroll prevention is active. |
hideScrollbar | MaybeAccessor<boolean> | true | Hide the <body> scrollbar while active. |
preventScrollbarShift | MaybeAccessor<boolean> | true | Compensate for the hidden scrollbar width to avoid layout shift. |
preventScrollbarShiftMode | MaybeAccessor<"padding" | "margin"> | "padding" | Which CSS property to use for the scrollbar shift compensation. |
restoreScrollPosition | MaybeAccessor<boolean> | true | Restore <body> scroll position via window.scrollTo when disabled. |
allowPinchZoom | MaybeAccessor<boolean> | false | Allow two-finger pinch-zoom gestures. |
Multiple active instances are stacked; only the topmost one installs event listeners. Body styles are shared and only restored once all instances clean up.
Primitive ideas:
PRs Welcome :)
createScrollTo- A primitive to support scroll to a targetcreateHashScroll- A primitive to support scrolling based on a hashtag change
Changelog
See CHANGELOG.md