use-resize-observer: measuring an element inside React
A React hook that allows you to use a ResizeObserver to measure an element's size.
At a glance
- What is it?
- use-resize-observer is a small React hook that wraps the browser ResizeObserver and reports an element's measured size to your component. It is under a kilobyte gzipped with no runtime dependencies, and its interesting design choice is that it defaults to a ref callback rather than a ref object, because that is what makes the awkward mount cases work.
- Who is it for?
- use-resize-observer fits a React component that needs a measured pixel value and would rather not write the attach and detach logic, the rounding policy and the ref timing case by hand. It does not fit a layout that only needs a style change, where CSS container queries remove the JavaScript entirely.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 84 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A hook that turns a ResizeObserver into a width and a height
The library is described in one sentence: a React hook that allows you to use a ResizeObserver to measure an element's size. That is the whole scope, and the README is honest about the division of labour. The measuring comes from the browser API, and the hook handles the React side of it, namely when to attach, when to detach, and when to cause a render.
The requirements are short. React 18.2 or newer, with the peer range open-ended so future majors work too, and a ResizeObserver implementation, which the README notes is available in all modern browsers. For an environment without one, the README gives two routes: polyfill it yourself, or skip observation entirely.
The API is a named export, and the response is three things: a ref callback to pass to React's ref prop, a width, and a height.
import { useResizeObserver } from "use-resize-observer";
const App = () => {
const { ref, width = 1, height = 1 } = useResizeObserver<HTMLDivElement>();
return (
<div ref={ref}>
Size: {width}x{height}
</div>
);
};Note the default values in the destructuring. The hook reports undefined before the first measurement, so the example substitutes 1 for both, which keeps the first render from producing NaN in a size calculation.
The hook is written in TypeScript, has zero runtime dependencies, ships both ESM and CJS builds, works with server side rendering, and works with CSS-in-JS. It is also tested in real browsers, with the README naming latest Chrome, Firefox, Edge and Safari plus real iOS and Android devices, sponsored by BrowserStack.
The ref callback default exists because ref objects are too late
One highlight line in the README explains the design better than any amount of documentation would: the hook uses a RefCallback by default, to address delayed mounts and changing ref elements.
A ref object has a well known race. The element is not attached when the effect runs, so a naive implementation either observes nothing on the first pass and depends on a later dependency to fire, or observes the wrong node when the component swaps elements. A ref callback solves both, because the callback fires at exactly the moment the node is attached and again when it is detached, so the hook can attach the observer to whatever node is currently there.
The hook does not force that choice on you. Custom refs are supported, so if you already had a ref you keep it and pass it in as an option, and the options table describes the ref option as a ref or element to observe, with the note that if it is omitted you use the ref callback the hook returns instead, which it marks as preferred.
That last word is the author's judgement, and it is the one to take seriously. If you pass your own ref object, you inherit the timing problem the hook was written to avoid, and the failures show up as a measurement that is one render stale rather than as an error, which is the harder kind of bug to notice.
The README also claims the hook handles many edge cases you might not even think of, and points at its documentation and test cases rather than enumerating them. That is a claim to verify in the tests rather than take on trust, and it is a reason to look at the repository's tests directory before trusting a measurement in production.
Callback-only mode takes rendering away from the hook
The options table has an entry that changes the contract of the hook entirely: the onResize callback.
Providing it opts into callback-only mode. The hook stops returning or updating width and height, and it no longer re-renders. You own the updates, and the README says you own the render optimisation as well. The callback receives the measured size together with the raw ResizeObserverEntry, so a handler can reach the entry directly when it needs the box data the observer reports.
That is a real escape hatch rather than a convenience. The default mode is convenient because the component re-renders when the size changes, which for a wide element can mean a lot of renders in a layout that animates or resizes continuously. In callback-only mode the hook measures and calls you, and you decide whether to store the value in state, in a ref, in a store, or not at all.
The trade-off is that callback-only mode pushes work onto the caller. If you put the size in state, you are back to re-rendering on every resize, and you have to decide how to avoid that, usually by rounding, throttling or writing to a ref. The hook's own answer to that problem is in the round option, which is about the default mode rather than this one.
For a library this small, having both modes is a sensible split: the simple case stays simple, and the case where a re-render per pixel is a problem has a documented path.
Box options are experimental, and a mismatch means no measurements at all
The box option selects which part of the box model is measured, and the README is careful about how much you can rely on it.
The content-box option is the default and is described as safe to use by all browsers that implemented ResizeObserver. Internally the hook falls back to contentRect from the old spec in case contentBoxSize is not available, which is the kind of detail that saves a debugging session.
The border-box option is described as supported well for the most part by evergreen browsers, with advice to feature-detect if older versions matter. The device-pixel-content-box option is the one to treat carefully: the README links an article by Surma explaining how it allows pixel perfect rendering, and notes that it has limited support, notably that it is unsupported in Safari, with a recommendation to feature-detect before relying on it.
The important failure mode is stated plainly: if the browser does not support the given box type, the hook will not report any sizes either. There is no fallback to the default box in that case. A silent zero, or a permanent undefined, is what you get when you ask for a box type the browser does not implement, and that is a debugging trap for anyone testing on one engine and shipping to another.
The README also notes that box options are experimental and are not supported by all browsers that implemented ResizeObserver, which is why the advice is feature detection rather than a table of support.
Rounding is what keeps a subpixel change from becoming a render
The default rounding is Math.round, and the reason given is specific: to avoid re-rendering on every subpixel change.
This is the quiet mechanism behind the hook's behaviour. A ResizeObserver fires when the observed box changes, and a fractional layout can change by fractions of a pixel as a container settles, as a scrollbar appears, or as a font loads. Without rounding, each of those produces a new width value and therefore a new render. With rounding to whole pixels, the reported number only changes when it visibly changes.
If you want different behaviour, the round option takes any function from number to number.
const { ref, width, height } = useResizeObserver<HTMLDivElement>({
round: Math.floor,
});Rounding down is the example given, and skipping rounding is shown as a no-op function declared outside the hook, with a comment explaining why it lives outside: so the instance does not change unnecessarily. That comment is a small lesson in itself, since a function created during render would be a new value on every render and could defeat the optimisation the hook is built on.
So the answer to whether you should use this hook is partly an answer about rounding. If your layout needs exact fractional sizes, for example when you are matching a canvas to a CSS box, rounding at the source is wrong and you need the no-op path. If you are laying out panels, the default is the behaviour you want and you should keep it.
Under one kilobyte, dual builds, and a size budget in CI
The packaging is designed for a library with no logic of its own beyond the hook. The size claim is under 1kB minified and gzipped, and it is not an aspiration in a badge: it is monitored by size-limit with a budget file named .size-limit.json, and there is a check:size script that runs it.
The build outputs are both module systems. The package is type module with sideEffects false, the main entry points at a CJS build and the module field at an ESM build, and the exports map splits the import and require conditions with matching declaration files for each. That is the shape a library needs when it has to be consumed by a bundler, by a Node CommonJS project and by a modern ESM toolchain without the consumer needing a transpiler.
The test setup is worth noting because it is broader than most hooks of this size. There are separate vitest projects for node and browser, a pack smoke test that exercises the published artefact, and an end to end suite driven by WebdriverIO. The scripts also include type checks across two tsconfig projects, one for tests and one for the end to end tests, plus formatting and linting through oxfmt and oxlint, with husky in the tree for the commit hooks.
The BrowserStack badge in the README is the part that carries the most weight for an adopter, because a measurement hook is exactly the kind of library that behaves differently in a real Safari on a real phone than in a headless browser. The README claims testing on the latest Chrome, Firefox, Edge and Safari plus real iOS and Android devices.
v10 breaks the API, and the browser API is the alternative
Two things an adopter has to plan for.
First, the major version. The README leads with a note that v10 has several breaking changes and points to a MIGRATION.md in the repository. The release list shows v10.0.0 on 2026-07-31, preceded on the same day by v10.0.0-alpha.2 and v10.0.0-alpha.3, so the majors are being shipped quickly and the migration file is the document to read before upgrading from v9.
Second, the alternative. The browser already ships the API, and using it directly in an effect is a few lines: create a ResizeObserver, observe the node, read the entry, disconnect on cleanup. The difference in approach is what the hook adds on top of that raw usage. The raw version measures and hands you entries, and everything else is yours: when to attach, what to do about a node that changes, how to avoid a render per pixel, how to behave when the element is not mounted yet.
The other alternative is to stop measuring in JavaScript at all and use CSS container queries, which the project lists as a topic. Container queries let CSS respond to the size of a container without any measurement code, so for a layout that only needs a style change, they remove the JavaScript entirely. They do not give your component numbers, so anything that needs a pixel value in JavaScript still needs the observer.
The judgement for an adopter is simple. If you only need a breakpoint in CSS, use container queries. If you need a measured number in React, use this hook and accept that it needs React 18.2 and a ResizeObserver implementation in the environment you target.
Editorial conclusion
use-resize-observer fits a React component that needs a measured pixel value and would rather not write the attach and detach logic, the rounding policy and the ref timing case by hand. It does not fit a layout that only needs a style change, where CSS container queries remove the JavaScript entirely. Before adopting it, read MIGRATION.md if you are on v9, and check that the box type you need is supported in the browsers you target, since an unsupported box means no measurements at all.
Frequently asked questions
How do I install use-resize-observer?
Add it with your package manager, for example pnpm add use-resize-observer, npm install use-resize-observer or yarn add use-resize-observer. The package ships ESM and CJS builds and has zero runtime dependencies.
What does useResizeObserver return?
It returns a ref callback to pass to React's ref prop, a width and a height, both of which are undefined before the first measurement, so the example substitutes 1 for each. The hook is a named export, not the default.
What is callback-only mode in use-resize-observer?
Passing the onResize option puts the hook into callback-only mode, where it stops returning and updating width and height and no longer re-renders. You own the updates and the render optimisation, and the callback receives the measured size together with the raw ResizeObserverEntry.
What are the requirements for use-resize-observer?
React 18.2 or newer, with an open-ended peer range, and a ResizeObserver implementation, which is available in all modern browsers. In an environment without one the README says to polyfill it or to skip observation entirely.
How do I stop use-resize-observer re-rendering on every pixel?
By default the measured values pass through Math.round, which the README describes as avoiding a re-render on every subpixel change. You can supply your own function through the round option, such as Math.floor, or a no-op to skip rounding entirely.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/zeecoder-use-resize-observer)Was this useful?