Fiber UI LogoFiberUI

useWindowScroll

A React hook that tracks the window scroll position.

Installation

npx shadcn@latest add https://r.fiberui.com/r/hooks/use-window-scroll.json

Features

  • 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

PropertyTypeDescription
xnumberHorizontal scroll position in pixels.
ynumberVertical 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;
}