Responsive Iframes in Chrome 154: Auto-Height in 3 Steps
Originally published on ExtensionBooster. Responsive iframes are finally a platform feature: since Chrome 154 hit Stable on September 22, 2026, an iframe can grow to fit its content with no script in the parent page. Th
Originally published on ExtensionBooster.
Responsive iframes are finally a platform feature: since Chrome 154 hit Stable on September 22, 2026, an iframe can grow to fit its content with no script in the parent page. The WHATWG request for this has been open since January 2016, and the classic Stack Overflow question about auto-height iframes has passed 1.5 million views. Here is the three-step setup, what you can delete, and how to ship it without breaking Firefox and Safari.
Why this matters
An iframe is a fixed viewport (300×150 px unless you size it). Taller content means a second scrollbar inside your page. For a decade the fix has been a two-sided protocol: the child measures itself, postMessage()s the height, and the parent sets style.height.
Chrome 154 replaces that with a double opt-in: a CSS property on the embedding side and a meta tag on the embedded side. It ships on desktop, Android and WebView, and the spec lives in the CSS Sizing Level 4 working draft.
The 3 steps
1. Embedder: set frame-sizing.
.embed {
width: 100%;
frame-sizing: content-height;
max-height: 80vh; /* optional guard against runaway embeds */
}
Available values: auto (default, old behavior), content-height, content-width, and the logical content-block-size / content-inline-size. For horizontal writing modes, content-height is the one you will use.
2. Embedded document: agree to share its size.
<meta name="responsive-embedded-sizing"
content="allow-origins=https://publisher1.example https://publisher2.example">
This tag must be in the HTML the server delivers. Google is explicit that it can't be injected after load, so a React head manager won't cut it. Use allow-origins=* only if any site may embed you.
3. Call requestResize() when content changes.
Sizing happens once, at load. That one-shot model is deliberate: it prevents the resize → reflow → resize loop. After you load more comments or expand an accordion, ask for a re-measure:
function render(items) {
list.replaceChildren(...items.map(toListItem));
if ('requestResize' in window) window.requestResize();
}
Call it once after the DOM update finishes. Don't wire it to a ResizeObserver that fires every frame.
What you can delete (and what you can't)
| Concern | postMessage resizer | frame-sizing |
|---|---|---|
| Parent code | Listener + height logic | One CSS declaration |
| Child code | Measure + postMessage | Meta tag + requestResize()
|
| Origin checks | Manual, easy to botch | Declarative allow-origins
|
| Jitter / loops | You debounce it | Avoided by design |
| Support | Every browser | Chrome 154+ |
One more reason to look at this: iframe-resizer v5 moved to GPL v3 or a paid commercial license (4.x was MIT), so closed-source teams have been pinning old versions or writing their own.
What you can't delete yet: Firefox and Safari haven't published a position. Keep the fallback and gate both paths:
.widget { width: 100%; height: 500px; }
@supports (frame-sizing: content-height) {
.widget { height: auto; frame-sizing: content-height; }
}
if (!CSS.supports('frame-sizing', 'content-height')) {
window.addEventListener('message', onHeightMessage); // legacy path
}
Also watch Core Web Vitals. Google warns the late resize can shift content, so give embeds a realistic min-height and avoid auto-sized embeds above the fold.
The extension angle
Browser extensions often inject an extension page as an iframe from a content script so the host page can't restyle or read the UI. Height was always the pain point. Now the pattern is:
- List
panel.htmlinweb_accessible_resourceswithmatcheslimited to the sites where the panel actually appears. - Put the meta tag in the panel's static HTML entry (not a component, whether you use WXT, Plasmo or CRXJS). The embedder origin is the website, not your extension.
- From the content script, set
frame-sizingwith!important(the iframe lives in the host DOM, so page CSS can target it), and fall back to a fixed height elsewhere:
const frame = document.createElement('iframe');
frame.src = chrome.runtime.getURL('panel.html');
if (CSS.supports('frame-sizing', 'content-height')) {
frame.style.setProperty('frame-sizing', 'content-height', 'important');
frame.style.setProperty('max-height', '80vh', 'important');
} else {
frame.style.setProperty('height', '420px', 'important');
}
document.body.append(frame);
- Call
requestResize()inside the panel after each render.
Chrome hasn't documented any special case for chrome-extension:// documents, so test on a real target site in Chrome 154+ before removing your old resizer.
Security note: height is data. The explainer warns that a page observing an iframe's laid-out size can learn things about the cross-origin document. If your panel's height depends on login state or result counts, any allowed embedder can read that. Scope allow-origins tightly, keep matches narrow, add frame-ancestors CSP on regular sites, and don't let a sensitive panel's size reflect private data before the user interacts.
Key takeaways
-
frame-sizing: content-height+ a server-rendered meta tag = auto-height iframes in Chrome 154+. - Sizing is one-shot; call
window.requestResize()after dynamic updates. - Firefox and Safari have no signal yet, so ship behind
@supportsand keep your resizer. - Reserve
min-heightto limit layout shift. - For extensions, treat iframe height as leakable state and scope origins narrowly.
The original article also covers iframe vs Shadow DOM trade-offs and other Chrome 154/155 changes, so read the full guide to responsive iframes and frame-sizing in Chrome 154 for the details. If you build extensions, ExtensionBooster's growth tools for Chrome extension developers can help after you ship.
Are you keeping your postMessage resizer as a fallback, or has iframe-resizer's license change already pushed you to roll your own? Tell me in the comments.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.