{"article":{"slug":"iframes-that-finally-fit-their-content","title":"Iframes that finally fit their content","subtitle":null,"summary":"Ahmad Elalfy explains Chrome 154's responsive iframes, which let an iframe grow to its content height with one CSS property, as long as the embedded page opts in with a meta tag. He compares it with the old postMessage and ResizeObserver approach and covers fallbacks, layout shift and the privacy risk of allowing any origin.","content_type":"tutorial","language":"en","canonical_url":"https://alfy.blog/2026/10/09/iframe-that-finally-fit-their-content.html","author":{"name":"Ahmad Elalfy","url":null,"person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"alfy.blog","url":"https://alfy.blog/","listing_slug":null,"listing":null},"topics":[{"name":"Web Development","slug":"web-development","url":"https://listedarticles.com/topics/web-development"},{"name":"CSS","slug":"css","url":"https://listedarticles.com/topics/css"},{"name":"Browsers","slug":"browsers","url":"https://listedarticles.com/topics/browsers"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":1287,"reading_minutes":6,"published_at":"2026-10-09T00:00:00.000Z","added_at":"2026-10-10T14:11:46.569Z","updated_at":"2026-10-10T14:11:46.569Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/iframes-that-finally-fit-their-content","markdown_url":"https://listedarticles.com/articles/iframes-that-finally-fit-their-content.md","example":false,"citation":"Ahmad Elalfy, alfy.blog. \"Iframes that finally fit their content.\" 9 Oct 2026. https://alfy.blog/2026/10/09/iframe-that-finally-fit-their-content.html (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://alfy.blog/2026/10/09/iframe-that-finally-fit-their-content.html"},"body_markdown":"Chrome 154 lets an iframe grow to the height of its content with one line of CSS without having to measure, without messages, and without resize scripts. But the page inside the iframe has to agree to it. That catch is the most interesting part of this feature, so this article spends some time on it.\n\n### The problem I faced\n\nI have built many checkout pages that use a payment provider’s iframe. The goal was always to have the card form feel like part of the page. The user should not notice that it comes from another website.\n\nThe iframe never helped with that. It has a fixed height. Make it too tall and you get an empty gap under the form. Make it too short and you get a scrollbar inside the page’s scrollbar. On mobile, that second scrollbar is the worst thing you can show someone who is about to pay. So I kept increasing the height, testing on different phones, and increasing it again.\n\nThis is a small problem. It should have a small solution. For years it did not.\n\n### How we used to do it\n\nThe parent page cannot look inside a cross-origin iframe. It does not know how tall the content is. So the only way was to make both pages talk to each other with JavaScript.\n\nInside the iframe, you measure the content and send the height to the parent:\n\n```\nconst send = () => {\n  const height = document.documentElement.scrollHeight;\n  parent.postMessage({ type: 'resize', height }, 'https://shop.example');\n};\n\nnew ResizeObserver(send).observe(document.body);\n```\n\nIn the parent page, you listen, check who sent the message, and set the height:\n\n```\nwindow.addEventListener('message', (event) => {\n  if (event.origin !== 'https://pay.example') return;\n  if (event.data?.type !== 'resize') return;\n\n  document.querySelector('#payment-frame').style.height = `${event.data.height}px`;\n});\n```\n\nThis looks short, but it hides a lot of problems:\n\n* **Width changes height.** On a small screen, text wraps more and the content gets taller. Every resize or phone rotation needs a new measurement.\n* **Content arrives late.** Fonts, images and error messages load after the first measurement. The height is wrong until someone measures again.\n* **Dynamic content.** If the page inside the iframe shows or hides elements after the initial load, the height changes and needs to be measured again.\n* **More than one iframe.** If the page has several, you need IDs in every message to know which one to resize.\n* **Both sides must cooperate.** If the third party does not send its height, there is nothing you can do. You go back to guessing a fixed height.\n\nThat last point did not go away with the new feature. It just moved into the browser.\n\n### The new way\n\nIn the parent page, you add one CSS property to the iframe:\n\n```\n.embed {\n  width: 100%;\n  frame-sizing: content-height;\n}\n```\n\nInside the iframe, the page opts in with a meta tag in its `<head>`. It also says which websites are allowed to size it:\n\n```\n<meta name=\"responsive-embedded-sizing\" content=\"allow-origins=https://shop.example\">\n```\n\nThat is all you need for content that does not change after it loads. The browser measures the content and sizes the iframe. No messages, no listeners, no origin checks in your code. The meta tag must be in the HTML from the start. Adding it later with JavaScript does not work.\n\nIf the content changes later (an error message appears, a section expands, more comments load), the page inside the iframe asks the browser to measure again:\n\n```\nwindow.requestResize();\n```\n\nSo JavaScript does not disappear completely. The embedded page still calls one function when its content changes. But the hard parts are gone. The browser does all of that now.\n\n`frame-sizing` also accepts `content-width`, `content-inline-size` and `content-block-size`. For most pages, `content-height` is the one you want. You can still combine it with limits like `max-height: 80vh`.\n\n### Where this helps\n\n#### Payment forms\n\nThis is the use case I care about most, and it is also the one that depends most on someone else.\n\nA payment form changes height all the time. An error appears under the card number. The user switches from card to wallet. A saved card list shows up. With `frame-sizing`, all of this could look smooth inside your checkout.\n\nBut you cannot turn it on from your side alone. You control the CSS on your checkout page. The payment provider controls the page inside the iframe. Only they can add the meta tag, list the merchant sites that are allowed, and call `requestResize()` when their form changes.\n\nSo for payments, the real question is not “does Chrome support this?” It is “does my payment provider support this?” Today, most providers either ship their own postMessage script or do nothing. If you work with one, ask them. If you build one, this is a cheap win for every merchant using you.\n\n#### Third-party widgets\n\nComment sections, contact forms, newsletter sign-ups and booking widgets all have the same problem. Their height depends on the content: the number of comments, the number of fields, the validation errors. These services already control their embedded page, so adding the meta tag is easy for them. The `allow-origins` list fits well here, because they already know which customer sites embed them. I have this exact problem on this website. My contact form is a Wufoo form inside an iframe, and I set its height by hand until it looked right.\n\n#### Multi-step forms and surveys\n\nEach step of a form has a different height. Step one has two fields. Step three has ten. Today, you either reserve space for the tallest step or let the iframe scroll. With this feature, the form calls `requestResize()` after each step and the iframe follows.\n\n#### Content you render yourself\n\nThis one does not need any third party. Many apps show HTML email previews, rich text previews, or code demos inside a sandboxed iframe using `srcdoc`. Here you write the embedded HTML yourself, so you can add the meta tag directly. This is probably the easiest place to start using the feature today.\n\n### Before you ship\n\n**Browser support.** This is Chromium-only for now. Firefox and Safari do not support it yet. Keep a fixed height as the default and switch to content sizing only where it works:\n\n```\n.embed {\n  width: 100%;\n  height: 500px;\n}\n\n@supports (frame-sizing: content-height) {\n  .embed {\n    height: auto;\n    frame-sizing: content-height;\n  }\n}\n```\n\nInside the iframe, check before calling the new function. Your old postMessage code can stay as a fallback until support grows:\n\n```\nif ('requestResize' in window) {\n  window.requestResize();\n}\n```\n\n**Layout shift.** The iframe loads after your page, then grows. Anything below it moves down. If the iframe is in the first screen, this can hurt your Core Web Vitals. A sensible `min-height` reduces the jump.\n\n**Do not use `allow-origins=*` without a reason.** Letting any site read your page’s height can leak information. For example, a page that is taller when the user is logged in tells the parent something about that user. List only the sites that need it. This works together with the CSP `frame-ancestors` rule, which controls who can embed you at all.\n\n### The browser does the boring part now\n\nFor years, a simple layout need, “make this box as tall as its content”, required two scripts on two websites that had to agree on a message format. Now it is one CSS property, one meta tag, and one function call when things change.\n\nThe browser side is ready in Chrome. The rest depends on the people who build the pages we embed. If you run a payment gateway, a comments service, or any widget that lives inside an iframe, add the meta tag. Your users’ checkouts and pages will feel like they were built as one piece.\n\nI will come back to payment providers in our region specifically in a follow-up post.\n\n### References\n\n* [Responsive iframes in Chrome 154](https://developer.chrome.com/blog/responsive-iframes), Chrome for Developers\n* [New in Chrome 154](https://developer.chrome.com/blog/new-in-chrome-154), Chrome for Developers","body_html":"<p>Chrome 154 lets an iframe grow to the height of its content with one line of CSS without having to measure, without messages, and without resize scripts. But the page inside the iframe has to agree to it. That catch is the most interesting part of this feature, so this article spends some time on it.</p>\n<h3 id=\"the-problem-i-faced\">The problem I faced</h3>\n<p>I have built many checkout pages that use a payment provider’s iframe. The goal was always to have the card form feel like part of the page. The user should not notice that it comes from another website.</p>\n<p>The iframe never helped with that. It has a fixed height. Make it too tall and you get an empty gap under the form. Make it too short and you get a scrollbar inside the page’s scrollbar. On mobile, that second scrollbar is the worst thing you can show someone who is about to pay. So I kept increasing the height, testing on different phones, and increasing it again.</p>\n<p>This is a small problem. It should have a small solution. For years it did not.</p>\n<h3 id=\"how-we-used-to-do-it\">How we used to do it</h3>\n<p>The parent page cannot look inside a cross-origin iframe. It does not know how tall the content is. So the only way was to make both pages talk to each other with JavaScript.</p>\n<p>Inside the iframe, you measure the content and send the height to the parent:</p>\n<pre><code>const send = () =&gt; {\n  const height = document.documentElement.scrollHeight;\n  parent.postMessage({ type: &#39;resize&#39;, height }, &#39;https://shop.example&#39;);\n};\n\nnew ResizeObserver(send).observe(document.body);</code></pre>\n<p>In the parent page, you listen, check who sent the message, and set the height:</p>\n<pre><code>window.addEventListener(&#39;message&#39;, (event) =&gt; {\n  if (event.origin !== &#39;https://pay.example&#39;) return;\n  if (event.data?.type !== &#39;resize&#39;) return;\n\n  document.querySelector(&#39;#payment-frame&#39;).style.height = `${event.data.height}px`;\n});</code></pre>\n<p>This looks short, but it hides a lot of problems:</p>\n<ul><li><strong>Width changes height.</strong> On a small screen, text wraps more and the content gets taller. Every resize or phone rotation needs a new measurement.</li><li><strong>Content arrives late.</strong> Fonts, images and error messages load after the first measurement. The height is wrong until someone measures again.</li><li><strong>Dynamic content.</strong> If the page inside the iframe shows or hides elements after the initial load, the height changes and needs to be measured again.</li><li><strong>More than one iframe.</strong> If the page has several, you need IDs in every message to know which one to resize.</li><li><strong>Both sides must cooperate.</strong> If the third party does not send its height, there is nothing you can do. You go back to guessing a fixed height.</li></ul>\n<p>That last point did not go away with the new feature. It just moved into the browser.</p>\n<h3 id=\"the-new-way\">The new way</h3>\n<p>In the parent page, you add one CSS property to the iframe:</p>\n<pre><code>.embed {\n  width: 100%;\n  frame-sizing: content-height;\n}</code></pre>\n<p>Inside the iframe, the page opts in with a meta tag in its <code>&lt;head&gt;</code>. It also says which websites are allowed to size it:</p>\n<pre><code>&lt;meta name=&quot;responsive-embedded-sizing&quot; content=&quot;allow-origins=https://shop.example&quot;&gt;</code></pre>\n<p>That is all you need for content that does not change after it loads. The browser measures the content and sizes the iframe. No messages, no listeners, no origin checks in your code. The meta tag must be in the HTML from the start. Adding it later with JavaScript does not work.</p>\n<p>If the content changes later (an error message appears, a section expands, more comments load), the page inside the iframe asks the browser to measure again:</p>\n<pre><code>window.requestResize();</code></pre>\n<p>So JavaScript does not disappear completely. The embedded page still calls one function when its content changes. But the hard parts are gone. The browser does all of that now.</p>\n<p><code>frame-sizing</code> also accepts <code>content-width</code>, <code>content-inline-size</code> and <code>content-block-size</code>. For most pages, <code>content-height</code> is the one you want. You can still combine it with limits like <code>max-height: 80vh</code>.</p>\n<h3 id=\"where-this-helps\">Where this helps</h3>\n<h4 id=\"payment-forms\">Payment forms</h4>\n<p>This is the use case I care about most, and it is also the one that depends most on someone else.</p>\n<p>A payment form changes height all the time. An error appears under the card number. The user switches from card to wallet. A saved card list shows up. With <code>frame-sizing</code>, all of this could look smooth inside your checkout.</p>\n<p>But you cannot turn it on from your side alone. You control the CSS on your checkout page. The payment provider controls the page inside the iframe. Only they can add the meta tag, list the merchant sites that are allowed, and call <code>requestResize()</code> when their form changes.</p>\n<p>So for payments, the real question is not “does Chrome support this?” It is “does my payment provider support this?” Today, most providers either ship their own postMessage script or do nothing. If you work with one, ask them. If you build one, this is a cheap win for every merchant using you.</p>\n<h4 id=\"third-party-widgets\">Third-party widgets</h4>\n<p>Comment sections, contact forms, newsletter sign-ups and booking widgets all have the same problem. Their height depends on the content: the number of comments, the number of fields, the validation errors. These services already control their embedded page, so adding the meta tag is easy for them. The <code>allow-origins</code> list fits well here, because they already know which customer sites embed them. I have this exact problem on this website. My contact form is a Wufoo form inside an iframe, and I set its height by hand until it looked right.</p>\n<h4 id=\"multi-step-forms-and-surveys\">Multi-step forms and surveys</h4>\n<p>Each step of a form has a different height. Step one has two fields. Step three has ten. Today, you either reserve space for the tallest step or let the iframe scroll. With this feature, the form calls <code>requestResize()</code> after each step and the iframe follows.</p>\n<h4 id=\"content-you-render-yourself\">Content you render yourself</h4>\n<p>This one does not need any third party. Many apps show HTML email previews, rich text previews, or code demos inside a sandboxed iframe using <code>srcdoc</code>. Here you write the embedded HTML yourself, so you can add the meta tag directly. This is probably the easiest place to start using the feature today.</p>\n<h3 id=\"before-you-ship\">Before you ship</h3>\n<p><strong>Browser support.</strong> This is Chromium-only for now. Firefox and Safari do not support it yet. Keep a fixed height as the default and switch to content sizing only where it works:</p>\n<pre><code>.embed {\n  width: 100%;\n  height: 500px;\n}\n\n@supports (frame-sizing: content-height) {\n  .embed {\n    height: auto;\n    frame-sizing: content-height;\n  }\n}</code></pre>\n<p>Inside the iframe, check before calling the new function. Your old postMessage code can stay as a fallback until support grows:</p>\n<pre><code>if (&#39;requestResize&#39; in window) {\n  window.requestResize();\n}</code></pre>\n<p><strong>Layout shift.</strong> The iframe loads after your page, then grows. Anything below it moves down. If the iframe is in the first screen, this can hurt your Core Web Vitals. A sensible <code>min-height</code> reduces the jump.</p>\n<p><strong>Do not use <code>allow-origins=*</code> without a reason.</strong> Letting any site read your page’s height can leak information. For example, a page that is taller when the user is logged in tells the parent something about that user. List only the sites that need it. This works together with the CSP <code>frame-ancestors</code> rule, which controls who can embed you at all.</p>\n<h3 id=\"the-browser-does-the-boring-part-now\">The browser does the boring part now</h3>\n<p>For years, a simple layout need, “make this box as tall as its content”, required two scripts on two websites that had to agree on a message format. Now it is one CSS property, one meta tag, and one function call when things change.</p>\n<p>The browser side is ready in Chrome. The rest depends on the people who build the pages we embed. If you run a payment gateway, a comments service, or any widget that lives inside an iframe, add the meta tag. Your users’ checkouts and pages will feel like they were built as one piece.</p>\n<p>I will come back to payment providers in our region specifically in a follow-up post.</p>\n<h3 id=\"references\">References</h3>\n<ul><li><a href=\"https://developer.chrome.com/blog/responsive-iframes\" rel=\"nofollow ugc noopener\">Responsive iframes in Chrome 154</a>, Chrome for Developers</li><li><a href=\"https://developer.chrome.com/blog/new-in-chrome-154\" rel=\"nofollow ugc noopener\">New in Chrome 154</a>, Chrome for Developers</li></ul>","headings":[{"level":3,"text":"The problem I faced","id":"the-problem-i-faced"},{"level":3,"text":"How we used to do it","id":"how-we-used-to-do-it"},{"level":3,"text":"The new way","id":"the-new-way"},{"level":3,"text":"Where this helps","id":"where-this-helps"},{"level":3,"text":"Before you ship","id":"before-you-ship"},{"level":3,"text":"The browser does the boring part now","id":"the-browser-does-the-boring-part-now"},{"level":3,"text":"References","id":"references"}]}}