Skip to main content

Overview

The useWindowWidth hook provides real-time tracking of the browser window width. It automatically updates when the window is resized, making it perfect for responsive layouts and breakpoint-based logic.

Hook Signature

const windowWidth = useWindowWidth();

Return Value

windowWidth
number
The current width of the browser window in pixels. Updates automatically on window resize.

Usage Examples

Persistent Player

From /home/daytona/workspace/source/src/components/PersistentPlayer/PersistentPlayer.jsx:16:
import useWindowWidth from "../../hooks/useWindowWidth";

const PersistentPlayer = ({ onClose }) => {
    const dispatch = useDispatch();
    const audioRef = useRef(null);
    const windowWidth = useWindowWidth();
    
    const [volume, setVolume] = useState(() => {
        const savedVolume = localStorage.getItem("nsnPlayerVolume");
        return savedVolume ? parseFloat(savedVolume) : 1;
    });

    // Use windowWidth for responsive player layout
    const isCompactMode = windowWidth < 768;
    
    return (
        <div className={isCompactMode ? styles.compact : styles.full}>
            {/* Player controls */}
        </div>
    );
};

Responsive Breakpoints

const windowWidth = useWindowWidth();

const isMobile = windowWidth < 640;
const isTablet = windowWidth >= 640 && windowWidth < 1024;
const isDesktop = windowWidth >= 1024;

return (
    <div>
        {isMobile && <MobileLayout />}
        {isTablet && <TabletLayout />}
        {isDesktop && <DesktopLayout />}
    </div>
);

Dynamic Column Layout

const windowWidth = useWindowWidth();

const getColumnCount = () => {
    if (windowWidth < 640) return 1;
    if (windowWidth < 1024) return 2;
    if (windowWidth < 1536) return 3;
    return 4;
};

const columns = getColumnCount();

return (
    <div style={{ gridTemplateColumns: `repeat(${columns}, 1fr)` }}>
        {/* Grid items */}
    </div>
);

Conditional Feature Display

const windowWidth = useWindowWidth();

const showSidebar = windowWidth >= 1024;
const showExpandedPlayer = windowWidth >= 768;

return (
    <div className={styles.container}>
        <main>{/* Main content */}</main>
        {showSidebar && <aside>{/* Sidebar */}</aside>}
        <footer>
            {showExpandedPlayer ? <ExpandedPlayer /> : <MinimalPlayer />}
        </footer>
    </div>
);

Implementation Details

Complete Source Code

import { useState, useEffect } from "react";

const useWindowWidth = () => {
    const [windowWidth, setWindowWidth] = useState(window.innerWidth);

    useEffect(() => {
        const handleResize = () => {
            setWindowWidth(window.innerWidth);
        };

        window.addEventListener("resize", handleResize);

        return () => {
            window.removeEventListener("resize", handleResize);
        };
    }, []);

    return windowWidth;
};

export default useWindowWidth;

Initial State

The hook initializes with the current window width:
const [windowWidth, setWindowWidth] = useState(window.innerWidth);

Resize Handling

A resize event listener updates the width whenever the window is resized:
const handleResize = () => {
    setWindowWidth(window.innerWidth);
};

window.addEventListener("resize", handleResize);

Cleanup

The event listener is properly cleaned up when the component unmounts:
return () => {
    window.removeEventListener("resize", handleResize);
};

Common Breakpoints

Standard responsive breakpoints to use with useWindowWidth:
BreakpointWidthUse Case
Mobile< 640pxSmall phones
Mobile Large< 768pxLarge phones
Tablet768px - 1024pxTablets
Desktop1024px - 1536pxSmall desktops
Desktop Large> 1536pxLarge screens

Best Practices

Define breakpoint constants to keep your responsive logic consistent across components:
const BREAKPOINTS = {
  mobile: 640,
  tablet: 768,
  desktop: 1024,
  wide: 1536
};

const windowWidth = useWindowWidth();
const isMobile = windowWidth < BREAKPOINTS.mobile;
Consider debouncing resize handlers if you’re performing expensive calculations based on window width. The hook itself is lightweight, but your derived logic might benefit from debouncing.
Avoid using this hook in many components simultaneously. Consider using a global state management solution or context if you need window width in multiple places to prevent duplicate event listeners.

Performance Considerations

Optimization with useMemo

import { useMemo } from 'react';

const windowWidth = useWindowWidth();

const layoutConfig = useMemo(() => ({
    columns: windowWidth < 640 ? 1 : windowWidth < 1024 ? 2 : 3,
    showSidebar: windowWidth >= 1024,
    playerHeight: windowWidth < 768 ? 60 : 80
}), [windowWidth]);

Debouncing Resize Events

For expensive operations, consider creating a debounced version:
import { useState, useEffect } from "react";

const useDebouncedWindowWidth = (delay = 150) => {
    const [windowWidth, setWindowWidth] = useState(window.innerWidth);

    useEffect(() => {
        let timeoutId;
        
        const handleResize = () => {
            clearTimeout(timeoutId);
            timeoutId = setTimeout(() => {
                setWindowWidth(window.innerWidth);
            }, delay);
        };

        window.addEventListener("resize", handleResize);

        return () => {
            clearTimeout(timeoutId);
            window.removeEventListener("resize", handleResize);
        };
    }, [delay]);

    return windowWidth;
};

Comparison with useMobileDetect

FeatureuseWindowWidthuseMobileDetect
Detection MethodWindow widthUser agent
GranularityExact pixel valueBoolean (mobile/not mobile)
Breakpoint ControlFull controlFixed detection
Updates OnWindow resizeInitial mount + resize
Best ForResponsive layoutsDevice-specific logic
Use useWindowWidth when you need precise control over breakpoints. Use useMobileDetect when you need to know if the device is actually mobile (for native features or mobile-specific UX).

Build docs developers (and LLMs) love