Reacting to an element changing size used to mean polling with setInterval or wiring up a brittle window.resize listener and hoping for the best. ResizeObserver is the browser-native fix — it fires a callback the moment any element changes size, for any reason, with the new dimensions handed to you directly.
⚡ Quick Answer
ResizeObserver is a JavaScript API that lets you watch any DOM element and run a callback whenever that element’s size changes — not just when the window resizes, but for any reason: a parent flexbox reflowing, content being added, a CSS class toggle, or a user dragging a resize handle. You create one observer, call .observe(element) on any elements you care about, and read the new dimensions from the callback’s entry object.
📋 Table of Contents
- Why ResizeObserver Exists
- Basic Syntax — The Three Lines You Need
- Reading the Entry Object
- borderBoxSize vs contentBoxSize — Which One to Use?
- inlineSize and blockSize — What Those Names Actually Mean
- Live Demo — Shape-Shifting Boxes
- Observing Multiple Elements with One Observer
- Cleanup: unobserve() and disconnect()
- Pitfalls to Know
- Browser Support
- ResizeObserver vs window.resize — Quick Comparison
- FAQ
Why ResizeObserver Exists
The old way of reacting to layout changes was window.addEventListener('resize', handler). That fires when the browser window resizes — but an element can change size for dozens of other reasons that have nothing to do with the window: a sidebar collapsing, a parent container going from flex-row to flex-column, text being dynamically inserted, a CSS animation changing a height, a user dragging a resize handle on a <textarea>.
Before ResizeObserver, the only alternatives were either hacks (a hidden <iframe> that fired resize events, looping getBoundingClientRect() calls in setInterval) or accepting that you simply couldn’t know. ResizeObserver is the browser saying: here’s a proper, efficient, callback-based API for exactly this problem.
Imagine you’re a window blind that needs to adjust every time the window frame changes size. window.resize is someone tapping you on the shoulder only when the outer wall of the building moves. ResizeObserver is a sensor attached directly to your frame — it fires the moment your frame moves, regardless of what caused it.
Basic Syntax — The Three Lines You Need
There are just three things you ever do with a ResizeObserver: create it with a callback, observe an element, and optionally stop watching later.
// 1. Create an observer with a callback
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
console.log('element resized:', entry.target);
}
});
// 2. Start watching an element
observer.observe(document.getElementById('my-element'));
// 3. Stop watching when done (e.g. component unmounts)
observer.unobserve(document.getElementById('my-element'));
for...of loop, even if you’re only watching one element.Reading the Entry Object
Each entry in the callback array is a ResizeObserverEntry object. These are the properties you’ll use:
| Property | What it gives you |
|---|---|
| entry.target | A reference to the actual DOM element that changed size. Use this to figure out which element fired when observing multiple elements with one observer. |
| entry.borderBoxSize[0] | An object with inlineSize and blockSize — the size of the element including padding and border. |
| entry.contentBoxSize[0] | An object with inlineSize and blockSize — the size of the element’s inner content area, excluding padding and border. |
| entry.contentRect | A legacy DOMRectReadOnly with familiar width and height properties. Easier to read, but considered a legacy API — prefer borderBoxSize or contentBoxSize in new code. |
[0] — why an array?[0] is always the one you want. This also means the old Firefox pattern of accessing entry.contentBoxSize.inlineSize directly (without the index) throws a TypeError in modern browsers — always use [0].borderBoxSize vs contentBoxSize — Which One to Use?
The difference maps directly to the CSS box model. Here’s a visual:
Use borderBoxSize when you want to know the total space the element occupies on screen — the number that matches what you’d see from getBoundingClientRect(). This is the right choice for most layout-reaction logic, including the demo in this tutorial.
Use contentBoxSize when you care about the space available for the element’s children specifically — for example, deciding how many columns to show inside a container without accounting for its own border.
inlineSize and blockSize — What Those Names Actually Mean
If you’re used to width and height, these names look strange at first. They’re writing-mode-aware equivalents:
| Property | Standard LTR layout | Vertical writing mode |
|---|---|---|
inlineSize | Width (horizontal) | Height (vertical) |
blockSize | Height (vertical) | Width (horizontal) |
“Inline” means “along the direction text flows” — left-to-right for English, so it maps to width. “Block” means “perpendicular to text flow” — top-to-bottom for English, so it maps to height. In Japanese vertical writing mode, those would swap. Using inlineSize/blockSize means your code stays correct regardless of which direction text flows — unlike hardcoding width and height, which are always physical.
For the vast majority of English-language sites with horizontal text, you can safely read inlineSize as width and blockSize as height.
Live Demo — Shape-Shifting Boxes
This is the demo from your code — two boxes that turn into circles when you resize them below 150×150px. Drag the bottom-right corner of either box to resize it. The shape change is driven entirely by ResizeObserver reading borderBoxSize.
↙ Drag the bottom-right corner of each box. Shrink below 150×150 to trigger the circle shape.
Each box independently tracks its own size. The shape changes when blockSize < 150 and inlineSize < 150 — exactly as written in the code below.
Here is the exact code powering this demo:
<div id="flex">
<div class="box box-1">A</div>
<div class="box box-2">B</div>
</div>
#flex {
display: flex;
gap: 12px;
}
.box {
border: 1px solid #ccc;
width: 300px;
height: 300px;
display: flex;
align-items: center;
justify-content: center;
resize: both; /* lets the user drag-resize */
overflow: hidden; /* required for `resize: both` to work */
}
.box-1 { background-color: beige; }
.box-2 { background-color: aqua; }
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
const target = entry.target;
const box = entry.borderBoxSize[0];
if (box.blockSize < 150 && box.inlineSize < 150) {
target.style.borderRadius = '100%';
} else {
target.style.borderRadius = 'unset';
}
}
});
const boxes = document.querySelectorAll('.box');
boxes.forEach((b) => {
observer.observe(b);
});
Notice what this code doesn’t need: no scroll listener, no polling, no window.resize. Each box independently triggers the callback the moment its own size crosses the 150px threshold in both dimensions. Both boxes are watched by a single observer, and entry.target tells the callback which box triggered each time.
Observing Multiple Elements with One Observer
One ResizeObserver can watch as many elements as you need. Calling .observe() again on the same observer simply adds another element to the watch list — it doesn’t replace the existing ones.
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
// entry.target tells you which element changed
console.log(entry.target.id, entry.borderBoxSize[0]);
}
});
observer.observe(document.getElementById('sidebar'));
observer.observe(document.getElementById('main-content'));
observer.observe(document.getElementById('footer'));
// All three reported in the same callback batch
entry.target to tell them apart inside the callback.Cleanup: unobserve() and disconnect()
Always stop observing elements when you no longer need them — especially elements that are removed from the DOM. An observer holding a reference to a detached node is a memory leak.
// Stop watching one specific element
observer.unobserve(element);
// Stop watching ALL elements and destroy the observer entirely
observer.disconnect();
| Method | When to use it |
|---|---|
unobserve(el) | You’re removing or hiding one specific element but keeping the observer active for others. |
disconnect() | You’re done with the observer entirely — component unmounts, page navigates, SPA route changes. |
Pitfalls to Know
The resize loop warning
If your callback sets a style on the element being observed that causes it to resize again, the browser catches this infinite loop and logs: “ResizeObserver loop completed with undelivered notifications.” It won’t break your page, but it means your callback is fighting itself.
// ❌ This causes a resize loop — the callback changes the element's width,
// which fires the observer again, which changes the width again...
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
entry.target.style.width =
entry.borderBoxSize[0].inlineSize + 10 + 'px';
}
});
width, height, padding, font-size) to the element you’re observing. Changing visual properties that don’t affect layout — like border-radius, color, or opacity — is perfectly safe and is what the demo above does.The callback fires immediately on observe()
As soon as you call observer.observe(element), the callback fires once with the element’s current size — even if nothing has changed yet. This is intentional and useful (you get the initial size for free), but it means your callback will run on page load, not just on later resize events. Make sure your callback logic is safe to run with whatever the initial size is.
It only works on the main thread
ResizeObserver is a Window-only API. You cannot create one inside a Web Worker. If you need size data in a worker, measure it on the main thread and postMessage the dimensions across.
Browser Support
ResizeObserver has excellent support across all modern browsers — around 96% global coverage as of 2026. Internet Explorer never supported it.
| Browser | ResizeObserver | borderBoxSize / contentBoxSize |
|---|---|---|
| Chrome / Edge | ✓ Chrome 64+, Edge 79+ | ✓ Chrome 84+ |
| Firefox | ✓ Firefox 69+ | ✓ Firefox 92+ (as array) |
| Safari | ✓ Safari 13.1+ (macOS), 13.4+ (iOS) | ✓ Safari 15.4+ |
| Internet Explorer | ✗ Never supported | ✗ Never supported |
entry.contentRect.width and entry.contentRect.height — the legacy contentRect property has broader support than the newer box-size properties, and is still in the spec for exactly this reason.ResizeObserver vs window.resize — Quick Comparison
| window.resize | ResizeObserver | |
|---|---|---|
| What it watches | The entire browser viewport | Any specific element(s) |
| Triggers on viewport resize? | ✓ Yes | ✓ Yes (if the element’s size changes) |
| Triggers on parent layout change? | ✗ No | ✓ Yes |
| Triggers on content change? | ✗ No | ✓ Yes |
| Gives you element dimensions | ✗ No (gives you window size) | ✓ Yes (directly on each entry) |
| Multiple targets | ✗ Only window | ✓ Unlimited elements |
Frequently Asked Questions
What is the ResizeObserver API?
ResizeObserver is a browser API that lets you run a JavaScript callback whenever a specific DOM element changes size — not just when the browser window resizes, but when any element changes for any reason: a parent layout shifting, content being added, a CSS class toggling, or a user dragging a resize handle.
What is the difference between inlineSize and blockSize?
inlineSize is the size along the direction text flows — horizontal width in a standard left-to-right layout. blockSize is perpendicular to that — vertical height in a standard layout. These names swap correctly in vertical or RTL writing modes, unlike physical width and height.
What is the difference between borderBoxSize and contentBoxSize?
borderBoxSize includes the element’s content, padding, and border — the total space it occupies. contentBoxSize measures only the inner content area, excluding padding and border. For most layout-reaction logic, borderBoxSize matches what CSS already calculated for the element’s visual size.
Should I use one ResizeObserver per element, or one for everything?
One observer can watch as many elements as you need — just call observe() on each. A single observer is more efficient because the browser batches and delivers all callbacks together. Use entry.target inside the callback to tell which element changed.
What is the ResizeObserver loop error?
The “ResizeObserver loop completed with undelivered notifications” warning fires when your callback changes the observed element’s size, which triggers another callback, creating an infinite loop. Avoid writing any dimensional change back to the element being observed — changing non-layout properties like border-radius is safe.
Summary
ResizeObserver gives you a clean, efficient way to react to any element changing size — something window.resize was never designed to handle.
- Create one observer with a callback, call
.observe(el)for every element you care about - Read
entry.targetto identify which element changed, andentry.borderBoxSize[0]for its new size - Use
inlineSizefor width andblockSizefor height in standard horizontal layouts - Always
unobserve()before removing a DOM element to avoid memory leaks - Never write a dimensional property back to the element you’re observing — that causes the resize loop warning
- The callback fires once immediately on
observe()— this is intentional and useful
