|$ curl https://forge-ai.dev/api/markdown?path=docs/html/iframes
$cat docs/iframes-&-embeds.md
updated This week·30 min read·published

Iframes & Embeds

HTMLIframesIntermediateIntermediate
Introduction

The <iframe> element embeds another HTML document within the current page. It creates a nested browsing context — a完全独立的 document with its own DOM, styles, and scripts. Iframes power everything from YouTube embeds and Google Maps to payment forms and social media widgets. While powerful, they introduce security, performance, and UX considerations that demand careful attention.

Every iframe starts with a src attribute pointing to the embedded document. The browser fetches that URL, parses it, and renders it inside a rectangular region defined by width and height. The embedded document runs in its own origin, which means it cannot access the parent page's DOM unless explicitly allowed.

📝

note

The term "iframe" stands for inline frame. It was first introduced by Microsoft Internet Explorer in 1997 and later standardized in HTML 4.01. Today it is part of the HTML Living Standard and supported in all browsers.
Basic iframe Syntax

The simplest iframe requires only a src attribute. However, production-ready iframes should always include name, width, height, title, and loading attributes. The name attribute is used as the targeting name for links and forms.

AttributeValuesDescription
srcURLURL of the page to embed
namestringTargetable name for links and forms
width / heightpixelsDimensions of the iframe (use CSS for responsive)
loadinglazy | eagerDefer loading until visible (lazy recommended)
sandboxspace-separated tokensRestricts capabilities of the embedded document
allowFeature Policy stringControls API access (microphone, camera, etc.)
referrerpolicyReferrerPolicy valueControls Referer header sent with the request
srcdocHTML stringInline HTML content instead of src URL
titlestringAccessible label (required for a11y)
iframe-basic.html
HTML
1<!-- Basic iframe -->
2<iframe
3 src="https://example.com/widget"
4 width="600"
5 height="400"
6 title="Example widget"
7></iframe>
8
9<!-- With name and lazy loading -->
10<iframe
11 src="https://example.com/map"
12 name="map-frame"
13 width="100%"
14 height="450"
15 title="Interactive map"
16 loading="lazy"
17></iframe>
18
19<!-- Links targeting a named iframe -->
20<iframe
21 src="about:blank"
22 name="content-frame"
23 width="800"
24 height="500"
25 title="Content viewer"
26></iframe>
27
28<a href="page1.html" target="content-frame">Page 1</a>
29<a href="page2.html" target="content-frame">Page 2</a>
30
31<!-- Inline content with srcdoc -->
32<iframe
33 srcdoc="<p>Hello from <strong>inside</strong> the iframe!</p>"
34 width="400"
35 height="100"
36 title="Inline content"
37 sandbox="allow-scripts"
38></iframe>
Security

Iframes are a prime vector for clickjacking, data exfiltration, and other attacks. The sandbox attribute is your primary defense — it applies a set of restrictions to the embedded content. Without sandbox, an iframe can run scripts, submit forms, open popups, and navigate the parent window.

Sandbox TokenAllows
allow-scriptsJavaScript execution
allow-same-originTreat content as same-origin (dangerous with allow-scripts)
allow-formsForm submission
allow-popupsOpening new windows via window.open
allow-popups-to-escape-sandboxPopups that are not sandboxed
allow-top-navigationNavigate the top-level window
allow-top-navigation-by-user-activationNavigate top window only on user gesture
allow-downloadsTrigger file downloads
allow-presentationStart a presentation session
allow-orientation-lockLock screen orientation

The sandbox attribute with no value enables all restrictions. You then opt-in to specific capabilities by adding tokens. This "default-deny" approach is the safest pattern:

iframe-security.html
HTML
1<!-- Maximum security: no sandbox tokens = all restrictions active -->
2<iframe
3 src="https://third-party.com/widget"
4 sandbox=""
5 title="Third-party widget"
6 width="400"
7 height="300"
8></iframe>
9
10<!-- Moderate security: scripts allowed, but same-origin blocked -->
11<iframe
12 src="https://third-party.com/form"
13 sandbox="allow-scripts allow-forms"
14 title="Contact form"
15 width="500"
16 height="400"
17></iframe>
18
19<!-- Minimal restrictions for trusted content -->
20<iframe
21 src="https://trusted-cdn.com/app"
22 sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
23 title="Trusted application"
24 width="800"
25 height="600"
26></iframe>
27
28<!-- Using allow for feature policy -->
29<iframe
30 src="https://app.example.com/video-call"
31 allow="camera 'src'; microphone 'src'; fullscreen"
32 sandbox="allow-scripts allow-same-origin"
33 title="Video call app"
34 width="640"
35 height="480"
36></iframe>
37
38<!-- referrerpolicy controls what Referer header is sent -->
39<iframe
40 src="https://analytics.example.com"
41 referrerpolicy="no-referrer"
42 sandbox="allow-scripts"
43 title="Analytics widget"
44 width="300"
45 height="200"
46></iframe>

danger

Never combine allow-scripts with allow-same-origin for untrusted content. This effectively removes all sandbox protections — the embedded page can escape the iframe, access the parent DOM, read cookies, and make same-origin requests. This is one of the most common iframe security mistakes.

The allow attribute (formerly Feature Policy, now Permissions Policy) controls access to browser APIs like camera, microphone, geolocation, and fullscreen:

iframe-permissions.html
HTML
1<!-- Grant camera and microphone access -->
2<iframe
3 src="https://meet.example.com/room"
4 allow="camera *; microphone *"
5 title="Video conference"
6></iframe>
7
8<!-- Allow fullscreen only -->
9<iframe
10 src="https://player.example.com/video"
11 allow="fullscreen"
12 title="Video player"
13></iframe>
14
15<!-- Geolocation with self + specific origin -->
16<iframe
17 src="https://maps.example.com"
18 allow="geolocation 'src' https://trusted-origin.com"
19 title="Map embed"
20></iframe>
Communicating with Parent (postMessage)

The postMessage() API enables cross-origin communication between a parent window and an iframe. Both sides must call postMessage and listen for message events. This is the only way for cross-origin iframes to exchange data safely — direct DOM access is blocked by the same-origin policy.

The pattern works in both directions: the parent can send messages to the iframe, and the iframe can send messages to the parent. The origin property on the event object must always be validated before trusting the message data.

postmessage.html
HTML
1<!-- Parent page: sending and receiving -->
2<iframe
3 src="https://widget.example.com"
4 id="widget-frame"
5 title="Widget"
6 sandbox="allow-scripts allow-same-origin"
7></iframe>
8
9<script>
10 const iframe = document.getElementById('widget-frame');
11
12 // Send a message to the iframe
13 iframe.contentWindow.postMessage(
14 { type: 'CONFIGURE', theme: 'dark', userId: 42 },
15 'https://widget.example.com'
16 );
17
18 // Listen for messages from the iframe
19 window.addEventListener('message', (event) => {
20 // ALWAYS verify the origin
21 if (event.origin !== 'https://widget.example.com') return;
22
23 const { type, data } = event.data;
24 if (type === 'USER_ACTION') {
25 console.log('Widget reported:', data);
26 updateUI(data);
27 }
28 });
29</script>
30
31<!-- Inside the iframe (widget.html): -->
32<script>
33 // Listen for messages from the parent
34 window.addEventListener('message', (event) => {
35 if (event.origin !== 'https://parent.example.com') return;
36
37 const { type, theme, userId } = event.data;
38 if (type === 'CONFIGURE') {
39 applyTheme(theme);
40 loadUserData(userId);
41 }
42 });
43
44 // Send results back to the parent
45 function notifyParent(action) {
46 window.parent.postMessage(
47 { type: 'USER_ACTION', data: action },
48 'https://parent.example.com'
49 );
50 }
51</script>

warning

Always validate event.origin before processing messages. Never use event.source alone to check the sender — it can be spoofed. Use the origin property with a whitelist of trusted origins. Additionally, always specify the target origin in postMessage() — using * leaks data to any listening window.
Common Embed Patterns

Third-party services provide embed codes that use iframes under the hood. Each service has specific requirements for sandbox attributes, allow policies, and URL parameters. Here are the most common embed patterns:

embeds.html
HTML
1<!-- YouTube video embed -->
2<iframe
3 width="560"
4 height="315"
5 src="https://www.youtube.com/embed/dQw4w9WgXcQ"
6 title="YouTube video player"
7 allow="accelerometer; autoplay; clipboard-write;
8 encrypted-media; gyroscope; picture-in-picture"
9 referrerpolicy="strict-origin-when-cross-origin"
10 sandbox="allow-scripts allow-same-origin allow-popups"
11></iframe>
12
13<!-- Google Maps embed -->
14<iframe
15 width="600"
16 height="450"
17 src="https://www.google.com/maps/embed?pb=!1m18..."
18 title="Google Maps location"
19 allow="geolocation"
20 sandbox="allow-scripts allow-same-origin"
21 loading="lazy"
22></iframe>
23
24<!-- CodePen embed -->
25<iframe
26 height="400"
27 src="https://codepen.io/username/embed/preview/abc123"
28 title="CodePen example"
29 sandbox="allow-scripts allow-same-origin allow-forms"
30 loading="lazy"
31></iframe>
32
33<!-- Spotify embed -->
34<iframe
35 src="https://open.spotify.com/embed/track/4cOdK2wGLETKBW3PvgPWqT"
36 width="300"
37 height="380"
38 title="Spotify track embed"
39 allow="autoplay; encrypted-media"
40 sandbox="allow-scripts allow-same-origin"
41 loading="lazy"
42></iframe>
43
44<!-- Vimeo embed -->
45<iframe
46 src="https://player.vimeo.com/video/123456789"
47 width="640"
48 height="360"
49 title="Vimeo video player"
50 allow="autoplay; fullscreen; picture-in-picture"
51 sandbox="allow-scripts allow-same-origin allow-popups"
52 loading="lazy"
53></iframe>

info

Most embed services provide a "Copy Embed Code" option that includes the correct sandbox and allow attributes. However, always audit these — some defaults are overly permissive. Add loading="lazy" yourself since many embed codes omit it.
Lazy Loading iframes

The loading attribute defers iframe loading until the iframe approaches the viewport. With loading="lazy", the browser does not fetch the iframe source until the user scrolls near it. This can dramatically reduce initial page weight and improve performance, especially for pages with multiple embeds.

ValueBehaviorUse Case
lazyDefers loading until near viewportEmbeds below the fold
eagerLoads immediately (default)Above-fold critical embeds

The browser typically uses a distance threshold of 3000-8000 pixels from the viewport. This means the iframe starts loading before the user reaches it, ensuring it is ready when scrolled into view. The exact threshold varies by browser and platform.

iframe-lazy.html
HTML
1<!-- Lazy-loaded iframe (below the fold) -->
2<iframe
3 src="https://example.com/heavy-widget"
4 width="600"
5 height="400"
6 title="Heavy widget"
7 loading="lazy"
8 sandbox="allow-scripts"
9></iframe>
10
11<!-- Eager iframe (above the fold — default) -->
12<iframe
13 src="https://example.com/critical-embed"
14 width="600"
15 height="400"
16 title="Critical embed"
17 loading="eager"
18 sandbox="allow-scripts"
19></iframe>
20
21<!-- Content-visibility CSS for additional deferred rendering -->
22<div style="content-visibility: auto; contain-intrinsic-size: 500px;">
23 <iframe
24 src="https://example.com/far-down-page"
25 width="600"
26 height="400"
27 title="Far down page"
28 loading="lazy"
29 sandbox="allow-scripts"
30 ></iframe>
31</div>

best practice

Add loading="lazy" to every iframe that is not initially visible. This includes embeds in tabs, accordions, carousels, and below-the-fold content. The performance savings are particularly significant for pages with many social media embeds. Only omit it for hero-section embeds that are critical to the initial user experience.
Responsive iframes

Native iframes have fixed dimensions set via width and height attributes. To make them fluid, use the CSS aspect-ratio property. This maintains the correct proportions across all screen sizes without JavaScript.

The classic "padding-bottom" hack is no longer needed — modern browsers support aspect-ratio universally. Combine it with width: 100% and height: auto for a fully responsive embed.

responsive-iframe.html
HTML
1<!-- Responsive iframe container -->
2<style>
3 .embed-container {
4 width: 100%;
5 max-width: 800px;
6 margin: 0 auto;
7 }
8
9 .responsive-iframe {
10 width: 100%;
11 height: auto;
12 aspect-ratio: 16 / 9;
13 border: none;
14 }
15
16 .video-wrapper {
17 width: 100%;
18 aspect-ratio: 16 / 9;
19 background: #000;
20 border-radius: 8px;
21 overflow: hidden;
22 }
23
24 .video-wrapper iframe {
25 width: 100%;
26 height: 100%;
27 border: none;
28 }
29
30 /* Different aspect ratios */
31 .square-frame {
32 aspect-ratio: 1 / 1;
33 }
34
35 .wide-frame {
36 aspect-ratio: 21 / 9;
37 }
38
39 .portrait-frame {
40 aspect-ratio: 9 / 16;
41 }
42</style>
43
44<!-- 16:9 responsive YouTube embed -->
45<div class="embed-container">
46 <div class="video-wrapper">
47 <iframe
48 src="https://www.youtube.com/embed/dQw4w9WgXcQ"
49 title="Responsive video"
50 allow="autoplay; fullscreen"
51 sandbox="allow-scripts allow-same-origin allow-popups"
52 loading="lazy"
53 ></iframe>
54 </div>
55</div>
56
57<!-- Square responsive embed -->
58<div class="embed-container">
59 <div class="video-wrapper square-frame">
60 <iframe
61 src="https://example.com/square-widget"
62 title="Square widget"
63 sandbox="allow-scripts"
64 loading="lazy"
65 ></iframe>
66 </div>
67</div>

Live preview of a responsive video embed container:

preview
Accessibility

The title attribute on an iframe provides the accessible name that screen readers announce when entering the iframe. This is the single most important accessibility requirement for iframes — without it, screen reader users hear only "frame" or "object" with no context.

Iframes that contain only decorative or hidden content should use aria-hidden="true" to remove them from the accessibility tree. However, title is still required for valid HTML and should describe the iframe's purpose even when hidden.

iframe-a11y.html
HTML
1<!-- Accessible iframe (always include title!) -->
2<iframe
3 src="https://example.com/widget"
4 title="Stock price ticker — updated in real time"
5 width="400"
6 height="200"
7 sandbox="allow-scripts"
8></iframe>
9
10<!-- Decorative/hidden iframe — hide from screen readers -->
11<iframe
12 src="https://example.com/tracking-pixel"
13 title="Analytics iframe"
14 aria-hidden="true"
15 tabindex="-1"
16 width="1"
17 height="1"
18 sandbox="allow-scripts"
19></iframe>
20
21<!-- Iframe that presents a form -->
22<iframe
23 src="https://example.com/checkout"
24 title="Secure payment form for completing your purchase"
25 width="600"
26 height="500"
27 sandbox="allow-scripts allow-forms allow-same-origin"
28 allow="payment"
29></iframe>
30
31<!-- Empty/placeholder iframe -->
32<iframe
33 src="about:blank"
34 title="Content area — navigation links will load pages here"
35 name="content-area"
36 width="100%"
37 height="500"
38></iframe>

best practice

Always provide a descriptive title attribute on every iframe. The title should describe the content and purpose of the embedded document, not just the source URL. For example, use "Weather forecast for San Francisco" instead of "widget.html". This is a WCAG Success Criterion 2.4.1 (Bypass Blocks) requirement.
Best Practices

Iframe Decision Guide

Avoid iframes when possible — prefer native HTML elements or server-side includes
Always sandbox third-party iframes — start with sandbox='' and add tokens selectively
Never combine allow-scripts with allow-same-origin for untrusted content
Always validate event.origin in postMessage handlers using a whitelist
Add loading='lazy' to every iframe below the fold to defer loading
Use aspect-ratio CSS for responsive iframes instead of the padding-bottom hack
Always include a descriptive title attribute for screen reader accessibility
Specify the exact target origin in postMessage() — never use '*'
Use the allow attribute to control API access (camera, microphone, fullscreen)
Set referrerpolicy='no-referrer' or 'strict-origin-when-cross-origin' for privacy
Avoid nesting iframes inside iframes — each level adds significant memory overhead
Consider the CSP frame-ancestors directive to prevent your own pages from being iframed

Security Checklist

Use sandbox attribute on all third-party iframes (start with no tokens, opt-in as needed)
Set a strict Content-Security-Policy with frame-src and frame-ancestors directives
Use the allow attribute to restrict API access to the minimum required
Never embed untrusted content without sandbox protection
X-Frame-Options: DENY on your own pages prevents clickjacking via iframes
Use Subresource Integrity (SRI) for iframes loading from CDNs when possible
Audit third-party embed codes before copying — remove overly permissive sandbox tokens

warning

Iframes are one of the most common security vulnerabilities on the web. Every third-party iframe is a potential vector for data exfiltration, clickjacking, or drive-by downloads. Treat iframes from untrusted sources with the same caution as executing arbitrary JavaScript. When in doubt, use the strictest sandbox configuration and test thoroughly.

Example of a hardened production iframe configuration:

iframe-production.html
HTML
1<!-- Production-hardened iframe -->
2<iframe
3 src="https://trusted-cdn.example.com/widget"
4 title="Weather widget — weekly forecast"
5 sandbox="allow-scripts"
6 allow=""
7 referrerpolicy="no-referrer"
8 loading="lazy"
9 width="100%"
10 height="auto"
11 style="aspect-ratio: 4 / 3; border: none; max-width: 400px;"
12></iframe>
$Blueprint — Engineering Documentation·Section ID: HTML-22·Revision: 1.0