useWindowScroll
A React hook that tracks the window scroll position.
Installation
npx shadcn@latest add https://r.fiberui.com/r/hooks/use-window-scroll.jsonFeatures
- Real-time Tracking - Updates coordinates as you scroll.
- Performance Optimized - Uses passive event listeners for smooth scrolling.
- SSR Safe - Handles server-side rendering gracefully.
Source Code
View the full hook implementation in the Hook Source Code section below.
Interactive Example
Scroll Position
Scroll the page to see the values update.
X0px
Y0px
Scrolled past 100px!
"use client";
import { useWindowScroll } from "@repo/hooks/dom/use-window-scroll";
import { ArrowDown, ArrowUp } from "lucide-react";
export const Example1 = () => {
const { x, y } = useWindowScroll();
return (
<div className="flex w-full max-w-md flex-col gap-4">
<div className="flex items-center gap-2">
{y > 100 ? (
<ArrowUp className="text-primary h-5 w-5" />
) : (
<ArrowDown className="text-muted-foreground h-5 w-5" />
)}
<h3 className="font-semibold">Scroll Position</h3>
</div>
<p className="text-muted-foreground text-sm">
Scroll the page to see the values update.
</p>
<div className="grid grid-cols-2 gap-4">
<ScrollValue label="X" value={x} />
<ScrollValue label="Y" value={y} />
</div>
<div
className={`text-center text-xs transition-opacity ${y > 100 ? "opacity-100" : "opacity-0"}`}
>
Scrolled past 100px!
</div>
</div>
);
};
const ScrollValue = ({ label, value }: { label: string; value: number }) => (
<div className="bg-muted/50 flex flex-col items-center justify-center rounded-lg p-4">
<span className="text-muted-foreground text-xs font-bold uppercase">
{label}
</span>
<span className="text-primary font-mono text-2xl">
{Math.round(value)}
</span>
<span className="text-muted-foreground text-xs">px</span>
</div>
);
const { x, y } = useWindowScroll();
if (y > 100) {
console.log("Scrolled past 100px!");
}Back to Top Button
Shows a button when scrolled down a certain amount.
Back to Top Button
Scroll down to see the button appear.
Scroll me down...
Keep scrolling...
Almost there...
Here we go!
(Note: This example uses window scroll, so you need to scroll the main page, not just the box above)
"use client";
import { useWindowScroll } from "@repo/hooks/dom/use-window-scroll";
import { ArrowUp } from "lucide-react";
export const Example2 = () => {
const { y } = useWindowScroll();
const scrollToTop = () => {
window.scrollTo({ top: 0, behavior: "smooth" });
};
return (
<div className="flex w-full max-w-md flex-col gap-4">
<h3 className="font-semibold">Back to Top Button</h3>
<p className="text-muted-foreground text-sm">
Scroll down to see the button appear.
</p>
<div className="bg-muted/20 h-40 w-full overflow-y-auto rounded-lg border p-4">
<div className="h-[500px] space-y-4">
<p>Scroll me down...</p>
<p>Keep scrolling...</p>
<p>Almost there...</p>
<p>Here we go!</p>
</div>
</div>
<button
onClick={scrollToTop}
disabled={y < 100}
className={`bg-primary text-primary-foreground fixed bottom-8 right-8 flex h-12 w-12 items-center justify-center rounded-full shadow-lg transition-all duration-300 ${
y > 100
? "translate-y-0 opacity-100"
: "pointer-events-none translate-y-10 opacity-0"
}`}
>
<ArrowUp className="h-6 w-6" />
<span className="sr-only">Back to top</span>
</button>
<p className="text-muted-foreground mt-2 text-xs">
(Note: This example uses window scroll, so you need to scroll
the main page, not just the box above)
</p>
</div>
);
};
const {y} = useWindowScroll();
const showButton = y > 100; API Reference
Signature
const { x, y } = useWindowScroll();Returns
| Property | Type | Description |
|---|---|---|
x | number | Horizontal scroll position in pixels. |
y | number | Vertical scroll position in pixels. |
Hook Source Code
import { useState, useCallback } from "react";
import { useEventListener } from "@repo/hooks/dom/use-event-listener";
interface ScrollPosition {
x: number;
y: number;
}
const isBrowser = typeof window !== "undefined";
/**
* useWindowScroll - Track window scroll position
*
* @returns Object with x and y scroll positions
*
* @example
* const { x, y } = useWindowScroll();
*/
export function useWindowScroll(): ScrollPosition {
const [position, setPosition] = useState<ScrollPosition>(() => ({
x: isBrowser ? window.scrollX : 0,
y: isBrowser ? window.scrollY : 0,
}));
const handleScroll = useCallback(() => {
setPosition({
x: window.scrollX,
y: window.scrollY,
});
}, []);
useEventListener("scroll", handleScroll, null, { passive: true });
return position;
}