Embedding a dynamic application inside an iframe sounds straightforward until the content starts changing height.
A photo grid loads more images. A customer opens a comment panel. A video player expands. Suddenly, the embedded content is cut off, surrounded by unnecessary whitespace, or trapped inside a second scrollbar.
Setting height: 100% usually doesn't solve the problem.
The real challenge is that the parent website and the embedded application may run on different origins. Browser security prevents the parent from directly inspecting the iframe's document dimensions.
Fortunately, there is a reliable way to handle this without weakening the browser's same-origin protections.
Why Fixed iframe Heights Fail
Consider an online gallery for photographers embedded inside a photography website.
The gallery might contain 20 images initially, but its height can change when:
- More images finish loading
- Responsive grids reorganize
- Clients expand comments
- Different fonts finish rendering
- Interactive panels open or close
- The browser viewport changes
A fixed iframe height cannot accurately represent all those states.
You could add internal scrolling, but that often creates a frustrating experience on mobile devices.
The better approach is to let the embedded application measure its own content and communicate the required height to the parent.
The Architecture: Two Documents, One Message Channel
The communication flow is simple:
Parent Website
|
| Embeds iframe
v
Embedded Application
|
| Measures content height
|
| postMessage()
v
Parent Website
|
| Validates message
|
| Updates iframe height
v
Responsive Embed
The iframe never needs direct access to the parent document.
The parent never needs permission to read the iframe's internal DOM.
Instead, both sides exchange a small, structured message.
Step 1: Prepare the iframe
The parent website starts with an iframe that has a reasonable fallback height.
<iframe
id="client-gallery"
title="Client photo gallery"
loading="lazy"
style="
display: block;
width: 100%;
height: 480px;
border: 0;
"
></iframe>
The embedding application will assign the approved iframe URL from its configuration.
The initial height matters because the iframe should remain usable even if JavaScript fails or the communication channel is unavailable.
Avoid using viewport-relative heights such as 100vh as the main resizing strategy. They describe the browser viewport, not the actual embedded content.
Step 2: Listen for Height Messages in the Parent
The parent needs to accept messages only from the expected iframe and its trusted origin.
Assume the application provides a trusted configuration containing the embed URL and its expected origin.
const iframe = document.querySelector("#client-gallery");
const config = window.GALLERY_EMBED_CONFIG;
if (new URL(config.url).origin !== config.origin) {
throw new Error("Unexpected embed origin");
}
window.addEventListener("message", (event) => {
if (event.origin !== config.origin) {
return;
}
if (event.source !== iframe.contentWindow) {
return;
}
const message = event.data;
if (
!message ||
message.type !== "gallery:resize" ||
message.version !== 1
) {
return;
}
if (
typeof message.height !== "number" ||
!Number.isFinite(message.height)
) {
return;
}
const height = Math.max(
300,
Math.min(20000, Math.ceil(message.height))
);
iframe.style.height = `${height}px`;
});
iframe.src = config.url;
There are several important details here.
First, event.origin must match the trusted application's origin.
Second, event.source must identify the expected iframe window.
Third, the received height must be validated before being applied.
The maximum height prevents an unexpected or malicious message from expanding the iframe indefinitely.
The trusted origin should come from application configuration, not from an arbitrary URL supplied by an untrusted user.
Step 3: Measure Content Inside the iframe
The embedded application knows its own layout.
It can measure the content and notify the parent whenever the relevant dimensions change.
A ResizeObserver works well for this.
const root = document.querySelector("#embed-root");
const parentOrigin = window.EMBED_PARENT_ORIGIN;
let previousHeight = 0;
let scheduled = false;
function publishHeight() {
scheduled = false;
if (window.parent === window) {
return;
}
const height = Math.ceil(
root.getBoundingClientRect().height
);
if (height === previousHeight) {
return;
}
previousHeight = height;
window.parent.postMessage(
{
type: "gallery:resize",
version: 1,
height
},
parentOrigin
);
}
function scheduleMeasurement() {
if (scheduled) return;
scheduled = true;
requestAnimationFrame(publishHeight);
}
const observer = new ResizeObserver(
scheduleMeasurement
);
observer.observe(root);
window.addEventListener(
"load",
scheduleMeasurement
);
document.fonts?.ready.then(
scheduleMeasurement
);
scheduleMeasurement();
The #embed-root element should contain the complete naturally sized application content, including relevant padding.
Avoid giving that root a height derived from the iframe viewport, such as min-height: 100vh. That can create a feedback loop between the parent's iframe height and the child's measurements.
Why ResizeObserver Is Better Than Polling
A common implementation periodically checks the document height.
setInterval(() => {
// Measure content height
}, 100);
This works, but it continuously performs measurements even when nothing changes.
ResizeObserver is event-driven.
It responds when the observed element's dimensions change, which makes it more appropriate for image grids, expandable interfaces, and dynamically loaded media.
The example also uses requestAnimationFrame to consolidate measurements within a rendering frame.
Another small optimization is comparing the new height with the previously reported value.
If nothing changed, no message is sent.
This reduces unnecessary cross-document communication and repeated style updates.
Security: Never Trust Every Message
postMessage is designed for cross-origin communication, but messages must still be treated as untrusted input.
A dangerous implementation would look like this:
window.addEventListener("message", (event) => {
iframe.style.height = event.data.height + "px";
});
This accepts data without verifying its source, structure, or value.
A secure implementation should validate:
- The sending origin
- The sending window
- The message type and version
- The expected data types
- Reasonable numeric limits
The embedded application should also specify an exact targetOrigin when sending messages rather than using "*".
For websites accepting embeds only from approved domains, the server can provide the appropriate parent origin.
Remember that resizing messages provide no authentication. Access to private photos, video files, comments, or downloads must still be authorized independently by the application server.
Common Problems Worth Testing
Several issues only become visible outside a desktop development environment.
Images loading late: The gallery may initially report a smaller height. Confirm that subsequent layout changes trigger another measurement.
Mobile layouts: Responsive grids can change from four columns to two or one. Test the iframe across different viewport widths.
Font loading: Web fonts may alter text dimensions. Measuring again after document.fonts.ready helps catch those changes.
Third-party cookies: Embedded authentication may behave differently because browsers restrict cross-site storage. Resizing does not solve authentication or session issues.
Content Security Policy: The embedded application's frame-ancestors policy must permit the intended parent website. The parent may also need an appropriate frame-src policy.
Sandbox restrictions: An iframe sandbox can change the embedded document's origin or disable scripts. Configure sandbox permissions deliberately rather than removing security restrictions blindly.
Keep the Communication Contract Small
It is tempting to expand the message channel until the parent knows everything about the embedded application.
Usually, that is unnecessary.
For resizing, the parent needs only a message type, protocol version, and requested height.
This separation helps preserve encapsulation.
The gallery can evolve internally without requiring changes to every website that embeds it.
The parent controls layout. The embedded application controls its content. Both communicate through a narrow, validated interface.
That is the central advantage of this architecture.
A responsive cross-origin iframe does not require bypassing browser security. It requires clear ownership of responsibilities, measured communication, and careful validation.
Once those boundaries are established, dynamic embedded applications can behave much more naturally across desktop and mobile websites.
Top comments (0)