Search across all documentation pages
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
import { useState, useEffect, useCallback, useRef } from "react";
interface WindowSize {
width: number;
height: number;
}
interface UseWindowSizeOptions {
/** Debounce delay in ms. Default: 100 */
debounceDelay?: number;
/** Initial size for SSR. Default: { width: 0, height: 0 } */
initialSize?: WindowSize;
}
function useWindowSize(options: UseWindowSizeOptions = {}): WindowSize {
const {
debounceDelay = 100,
initialSize = { width: 0, height: 0 },
} = options;
const [size, setSize] = useState<WindowSize>(() => {
if (typeof window === "undefined") return initialSize;
return {
width: window.innerWidth,
height: window.innerHeight,
};
});
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => {
if (typeof window === "undefined") return;
const handleResize = () => {
if (timerRef.current) clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
setSize({
width: window.innerWidth,
height: window.innerHeight,
});
timerRef.current = null;
}, debounceDelay);
};
// Set initial size on mount
setSize({
width: window.innerWidth,
height: window.innerHeight,
});
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
if (timerRef.current) clearTimeout(timerRef.current);
};
}, [debounceDelay]);
return size;
}When to reach for this: You need window dimensions in JavaScript for responsive layout calculations, canvas sizing, virtualized lists, or conditional rendering that CSS media queries cannot handle.
"use client";
function ResponsiveLayout() {
const { width, height } = useWindowSize();
const columns = width >= 1024 ? 3 : width >= 640 ? 2 : 1;
return (
<div>
<p>
Window: {width} x {height}
</p>
<div
style={{
display: "grid",
gridTemplateColumns: `repeat(${columns}, 1fr)`,
gap: 16,
}}
>
{Array.from({ length: 6 }, (_, i) => (
<div
key={i}
style={{
padding: 24,
background: "#f5f5f5",
borderRadius: 8,
textAlign: "center",
}}
>
Card {i + 1}
</div>
))}
</div>
</div>
);
}
function CanvasSizer() {
const { width } = useWindowSize({ debounceDelay: 200 });
const canvasWidth = Math.min(width - 32, 800);
const canvasHeight = canvasWidth * 0.5625; // 16:9
return (
<canvas
width={canvasWidth}
height={canvasHeight}
style={{ border: "1px solid #ccc" }}
/>
);
}What this demonstrates:
useState reads window.innerWidth and window.innerHeight only on the client, falling back to initialSize during SSR.setTimeout-based debounce collapses rapid resize events into a single state update, preventing jank.| Option | Type | Default | Description |
|---|---|---|---|
debounceDelay | number | 100 | Milliseconds to debounce resize events |
initialSize | { width, height } | { width: 0, height: 0 } | Size returned during SSR |
| Return | Type | Description |
|---|---|---|
width | number | Current window.innerWidth |
height | number | Current window.innerHeight |
Without debounce: For immediate updates (e.g., drag-resize previews), set debounceDelay: 0 or remove the setTimeout:
const size = useWindowSize({ debounceDelay: 0 });With orientation: Detect landscape vs portrait:
function useOrientation() {
const { width, height } = useWindowSize();
return width > height ? "landscape" : "portrait";
}Document size (scroll height): Track the full document height instead of viewport:
// Inside the resize handler:
setSize({
width: document.documentElement.scrollWidth,
height: document.documentElement.scrollHeight,
});WindowSize interface is exported so consumers can type their own state or props.{ width: number; height: number }.initialSize (0x0), but the client immediately updates to real dimensions. Fix: This causes a layout shift on first render. For critical layouts, prefer CSS media queries or provide a reasonable initialSize estimate.debounceDelay as needed.window.visualViewport API if you need to distinguish keyboard from resize.window.innerWidth reflects the iframe size, not the parent window. Fix: Use parent.window if cross-origin policy allows, or pass size as a prop.| Package | Hook Name | Notes |
|---|---|---|
usehooks-ts | useWindowSize | No built-in debounce |
@uidotdev/usehooks | useWindowSize | Minimal implementation |
ahooks | useSize | Tracks any element, not just window |
react-use | useWindowSize | Includes server-side defaults |
@react-hook/window-size | useWindowSize | Throttled variant available |
Without debouncing, every pixel of a resize drag triggers a state update and re-render. The built-in debounce collapses rapid events into a single update, preventing jank and wasted renders.
Set debounceDelay to 0:
const size = useWindowSize({ debounceDelay: 0 });This still uses setTimeout(..., 0) which defers to the next tick. For truly synchronous updates, remove the setTimeout from the hook.
initialSize provides the dimensions returned during SSR (default: { width: 0, height: 0 }).{ width: 1024, height: 768 }) to reduce layout shift.The component may have been unmounted and remounted while the window was resized. The immediate set ensures the state is correct even if no resize event fires after mount.
initialSize that matches your most common viewport.useMediaQuery for boolean breakpoint checks that can tolerate the brief flash.On mobile browsers, the virtual keyboard resizes the viewport. The hook fires on any resize, including keyboard open/close. Use window.visualViewport API to distinguish keyboard events from actual window resizes.
useWindowSize returns exact pixel values (width, height).useMediaQuery returns a boolean for a specific breakpoint.useWindowSize when you need calculations (e.g., canvas sizing, column counts).useMediaQuery when you only need a boolean toggle.Yes. Replace window.innerWidth/Height with document.documentElement.scrollWidth/Height inside the resize handler. This gives the total document size including overflow.
The hook returns WindowSize, which is { width: number; height: number }. The interface is exported so consumers can use it for their own props or state types.
function useOrientation(): "landscape" | "portrait" {
const { width, height } = useWindowSize();
return width > height ? "landscape" : "portrait";
}Reviewed by Chris St. John·Last updated Jul 10, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥