HTML Guides for iframe
Learn how to identify and fix common HTML validation errors flagged by the W3C Validator — so your pages are standards-compliant and render correctly across every browser. Also check our Accessibility Guides.
The seamless attribute was originally drafted as part of the HTML5 specification to allow an <iframe> to appear as though its content were part of the containing document. When present, it was supposed to remove borders, inherit styles from the parent page, and allow the iframe content to participate in the parent document’s styling context. However, no browser ever fully implemented the attribute, and it was officially removed from the WHATWG HTML Living Standard.
Because seamless is not a recognized attribute in the current HTML specification, using it triggers a validation error. Beyond validation, including it has no practical effect in any modern browser — it’s simply ignored. Keeping unsupported attributes in your markup creates confusion for other developers who may assume the attribute is doing something meaningful. It also adds unnecessary clutter to your HTML.
How to Fix It
Remove the seamless attribute from any <iframe> elements. If you want to replicate the visual effects that seamless was intended to provide, you can use CSS:
- Remove the border: Apply border: none; or use the frameborder="0" attribute (though frameborder is also obsolete — CSS is preferred).
- Blend the background: Set background: transparent; and add the allowtransparency attribute if targeting older browsers.
- Auto-resize to content: Use JavaScript to dynamically adjust the iframe’s height based on its content (subject to same-origin restrictions).
Note that true seamless integration — where the iframe inherits parent styles and its content flows naturally within the parent document — is not achievable with standard iframe behavior. If you need that level of integration, consider including the content directly in the page, using a server-side include, or fetching the content with JavaScript via the fetch API and inserting it into the DOM.
Examples
❌ Invalid: Using the seamless attribute
<iframe src="widget.html" seamless></iframe>
✅ Fixed: Attribute removed, CSS used for styling
<iframe src="widget.html" style="border: none;"></iframe>
✅ Fixed: Using a CSS class for cleaner markup
<style>
.seamless-iframe {
border: none;
width: 100%;
background: transparent;
}
</style>
<iframe src="widget.html" class="seamless-iframe" title="Embedded widget"></iframe>
✅ Alternative: Embedding content directly instead of using an iframe
If the content is on the same origin and you need true seamless integration, consider loading it directly:
<div id="embedded-content">
<!-- Content loaded via server-side include or JavaScript fetch -->
</div>
The <noscript> element behaves differently depending on where it appears in a document. When placed inside the <head>, it can only contain <link>, <style>, and <meta> elements — strictly metadata content. When placed inside the <body>, it can contain any flow content, including <p>, <div>, <iframe>, and more. This distinction is defined in the WHATWG HTML Living Standard.
When the validator encounters an <iframe> inside a <noscript> in the <head>, it reports “Bad start tag” because the parser is operating under the <head> content model, where <iframe> is simply not a valid element. The browser may attempt error recovery by implicitly closing the <head> and opening the <body>, but this can lead to unexpected DOM structures and layout issues.
This pattern commonly appears when adding tracking or analytics snippets. For instance, Google Tag Manager provides a <noscript> fallback containing an <iframe>, and it’s meant to be placed immediately after the opening <body> tag — not in the <head>. Placing it in the wrong location breaks validation and may cause the tracking pixel to malfunction.
To fix the error, identify any <noscript> blocks in your <head> that contain non-metadata elements (like <iframe>, <p>, <div>, <img>, etc.) and move them into the <body>. If the <noscript> block only needs metadata elements like <meta> or <style>, it can remain in the <head>.
Examples
Invalid: iframe inside noscript in head
The <iframe> is not valid metadata content, so it cannot appear inside <noscript> within the <head>.
<!DOCTYPE html>
<html lang="en">
<head>
<title>My webpage</title>
<noscript>
<iframe src="https://example.com/tracking"></iframe>
</noscript>
</head>
<body>
<h1>Welcome</h1>
</body>
</html>
Fixed: noscript with iframe moved to body
Moving the <noscript> block into the <body> resolves the error, since flow content is allowed there.
<!DOCTYPE html>
<html lang="en">
<head>
<title>My webpage</title>
</head>
<body>
<noscript>
<iframe src="https://example.com/tracking"></iframe>
</noscript>
<h1>Welcome</h1>
</body>
</html>
Valid: metadata-only noscript in head
A <noscript> block that contains only metadata elements is perfectly valid inside the <head>.
<!DOCTYPE html>
<html lang="en">
<head>
<title>My webpage</title>
<noscript>
<meta http-equiv="refresh" content="0; url=https://example.com/nojs">
<style>
.js-only { display: none; }
</style>
</noscript>
</head>
<body>
<h1>Welcome</h1>
</body>
</html>
Fixed: splitting a mixed noscript between head and body
If you need both metadata and flow content in your <noscript> fallback, use two separate <noscript> blocks — one in the <head> for metadata, and one in the <body> for visible content.
<!DOCTYPE html>
<html lang="en">
<head>
<title>My webpage</title>
<noscript>
<style>
.js-only { display: none; }
</style>
</noscript>
</head>
<body>
<noscript>
<p>Please enable JavaScript to view this website.</p>
<iframe src="https://example.com/tracking"></iframe>
</noscript>
<h1>Welcome</h1>
</body>
</html>
The value allow-storage-access-by-user-activation is not a valid keyword for the sandbox attribute of the iframe, as it’s still an experimental value.
The sandbox attribute enables an extra set of restrictions for the content in an iframe. You can add specific keywords to relax certain restrictions, such as allow-scripts, allow-forms, allow-popups, and others.
The experimental value allow-storage-access-by-user-activation is not yet a part of the official list of allowed keywords recognized by the W3C validator or the WHATWG HTML standard.
Illegal character “[” in the iframe src URL requires percent-encoding or removal.
The iframe element’s src must be a valid URL. According to URL syntax, characters like [ and ] are not allowed in the query unless percent-encoded. If your src contains array-like parameters (e.g., filters[category]=news), encode reserved characters: [ becomes %5B and ] becomes %5D. Avoid spaces and encode other reserved characters as needed. Alternatively, adjust the server to accept dot or bracketless notation (e.g., filters.category=news or filters_category=news) so the URL stays valid without encoding.
HTML Examples
Example causing the validator error
<!DOCTYPE html>
<html lang="en">
<head>
<title>Iframe URL Error</title>
</head>
<body>
<!-- [ and ] are illegal in URLs unless encoded -->
<iframe src="https://example.com/embed?filters[category]=news&filters[tags]=web"></iframe>
</body>
</html>
Fixed example with percent-encoding
<!DOCTYPE html>
<html lang="en">
<head>
<title>Iframe URL Fixed</title>
</head>
<body>
<!-- Encode [ as %5B and ] as %5D -->
<iframe src="https://example.com/embed?filters%5Bcategory%5D=news&filters%5Btags%5D=web"></iframe>
</body>
</html>
The src attribute on an <iframe> tells the browser which document to load and display within the embedded frame. When you leave this attribute empty (src=""), the W3C HTML Validator reports an error because the HTML specification requires the value to be a valid, non-empty URL. An empty string doesn’t resolve to a meaningful resource.
This is more than a cosmetic validation issue. When a browser encounters src="", it typically interprets the empty string as a relative URL pointing to the current page, which causes the browser to re-fetch and embed the current document inside itself. This leads to unnecessary network requests, potential performance degradation, and unexpected rendering behavior. In some cases it can even cause infinite nesting loops or break page functionality.
From an accessibility standpoint, an empty <iframe> with no valid source provides no meaningful content to assistive technologies. Screen readers may announce the frame without being able to describe its purpose, creating a confusing experience for users.
How to fix it
- Provide a valid URL: Set the src attribute to the actual URL of the content you want to embed.
- Use about:blank for intentionally empty frames: If you need an <iframe> in the DOM but don’t have content to load yet (e.g., you plan to populate it later via JavaScript), use src="about:blank". This is a valid URL that loads a blank page without triggering extra requests.
- Remove the element: If the <iframe> isn’t needed, remove it from the markup entirely. You can dynamically create and insert it with JavaScript when you have content to load.
- Use srcdoc instead: If you want to embed inline HTML rather than loading an external URL, use the srcdoc attribute, which accepts an HTML string directly.
Examples
❌ Empty src attribute
<iframe src=""></iframe>
This triggers the validation error because the src value is an empty string.
❌ src attribute with only whitespace
<iframe src=" "></iframe>
Whitespace-only values are also invalid URLs and will produce the same error.
✅ Valid URL in src
<iframe src="https://example.com/map.html" title="Location map"></iframe>
A fully qualified URL is the most straightforward fix. Note the title attribute, which is recommended for accessibility so that assistive technologies can describe the frame’s purpose.
✅ Using about:blank for a placeholder frame
<iframe src="about:blank" title="Dynamic content area"></iframe>
This is a valid approach when you need the <iframe> in the DOM but plan to set its content later with JavaScript using contentDocument.write() or by updating the src attribute dynamically.
✅ Using srcdoc for inline content
<iframe srcdoc="<p>Hello, embedded world!</p>" title="Inline content"></iframe>
The srcdoc attribute lets you embed HTML directly without needing an external URL. When srcdoc is present, it takes priority over src.
✅ Dynamically creating the iframe with JavaScript
<div id="video-container"></div>
<script>
const iframe = document.createElement("iframe");
iframe.src = "https://example.com/video.html";
iframe.title = "Video player";
document.getElementById("video-container").appendChild(iframe);
</script>
If the source URL isn’t available at page load, creating the <iframe> dynamically avoids having an empty src in your HTML altogether.
The HTML specification defines that the width and height attributes on <iframe> elements must contain a valid non-negative integer — that is, a string of one or more digits representing a number zero or greater (e.g., "0", "300", "600"). When one of these attributes is set to an empty string (width="" or height=""), the validator raises this error because an empty string cannot be parsed as a valid integer.
This commonly happens when a CMS, template engine, or JavaScript framework outputs an <iframe> with a dynamic dimension value that ends up being blank. It can also occur when developers remove the value but leave the attribute in place, or when copy-pasting embed code and accidentally clearing the value.
While most browsers will fall back to their default iframe dimensions (typically 300×150 pixels) when they encounter an empty value, relying on this behavior is not standards-compliant. Invalid attribute values can cause unpredictable rendering across different browsers, interfere with layout calculations, and make your markup harder to maintain. Assistive technologies may also have trouble determining the intended dimensions of the iframe.
How to fix it
You have a few options:
- Set a valid integer value. If you know the desired dimensions, specify them directly as non-negative integers. The values represent pixels.
- Remove the attribute entirely. If you don’t need to set dimensions via HTML attributes, remove the empty width or height attribute. The browser will apply its default size, or you can control sizing with CSS.
- Use CSS instead. For responsive designs or more flexible sizing, remove the HTML attributes and use CSS properties like width, height, max-width, or aspect-ratio.
Note that these attributes accept only plain integers — no units, no percentages, and no decimal points. For example, width="600" is valid, but width="600px" or width="100%" is not.
Examples
❌ Invalid: empty string values
<iframe src="https://example.com" width="" height=""></iframe>
Both width and height are set to empty strings, which are not valid non-negative integers.
✅ Fixed: specify valid integer values
<iframe src="https://example.com" width="600" height="400"></iframe>
✅ Fixed: remove the empty attributes
<iframe src="https://example.com"></iframe>
The browser will use its default dimensions (typically 300×150 pixels).
✅ Fixed: remove attributes and use CSS for sizing
<iframe src="https://example.com" style="width: 100%; height: 400px;"></iframe>
This approach is especially useful for responsive layouts where a fixed pixel width in HTML doesn’t make sense.
✅ Fixed: responsive iframe with CSS aspect ratio
<iframe
src="https://example.com/video"
style="width: 100%; aspect-ratio: 16 / 9; border: none;">
</iframe>
Using aspect-ratio in CSS lets the iframe scale responsively while maintaining its proportions, without needing width or height attributes at all.
In HTML, boolean attributes like allowfullscreen, disabled, readonly, and hidden work differently from what many developers expect. Their presence alone on an element means “true,” and their absence means “false.” The only valid values for a boolean attribute are the empty string ("") or the attribute’s own name (e.g., allowfullscreen="allowfullscreen"). Setting a boolean attribute to "true" is not valid HTML according to the WHATWG HTML living standard, even though browsers will typically still interpret it as enabled.
This matters for several reasons. First, it violates the HTML specification and will produce a W3C validation error. Second, it can create confusion for other developers who may assume that setting the attribute to "false" would disable it — but that’s not how boolean attributes work. Setting allowfullscreen="false" would still enable fullscreen because the attribute is present. Keeping your markup valid and semantically correct avoids these misunderstandings and ensures forward compatibility with browsers and tooling.
It’s also worth noting that allowfullscreen is now considered a legacy attribute. The modern approach is to use the allow attribute with the "fullscreen" permission token, which is part of the broader Permissions Policy mechanism. The allow attribute gives you fine-grained control over multiple features in a single attribute.
Examples
Incorrect: boolean attribute set to “true”
This triggers the validation error because "true" is not a valid value for a boolean attribute.
<iframe src="https://example.com" allowfullscreen="true"></iframe>
Correct: boolean attribute with no value
Simply include the attribute name without any value assignment.
<iframe src="https://example.com" allowfullscreen></iframe>
Also correct: boolean attribute with an empty string
The empty string is a valid value for any boolean attribute.
<iframe src="https://example.com" allowfullscreen=""></iframe>
Also correct: boolean attribute set to its own name
Per the spec, a boolean attribute can be set to a case-insensitive match of its own name.
<iframe src="https://example.com" allowfullscreen="allowfullscreen"></iframe>
Recommended: using the modern allow attribute
The allow attribute is the preferred approach going forward. It replaces allowfullscreen and can also control other permissions like camera, microphone, and more.
<iframe src="https://example.com" allow="fullscreen"></iframe>
You can combine multiple permissions in a single allow attribute:
<iframe src="https://example.com" allow="fullscreen; camera; microphone"></iframe>
Common mistake: trying to disable with “false”
Be aware that the following does not disable fullscreen — the attribute is still present, so fullscreen is still allowed. This would also produce a validation error.
<!-- This does NOT disable fullscreen — and is invalid HTML -->
<iframe src="https://example.com" allowfullscreen="false"></iframe>
To disable fullscreen, simply omit the attribute entirely:
<iframe src="https://example.com"></iframe>
The height attribute on <iframe> is defined in the HTML specification as a “valid non-negative integer.” This means the value must be a string of one or more digit characters (0 through 9) and nothing else. Unlike CSS properties, this attribute does not accept units like px or %, nor does it accept decimal values like 315.5. Even invisible characters such as leading or trailing spaces will trigger this validation error because the parser expects a digit and encounters something else.
This matters for several reasons. While browsers are generally forgiving and may still render the iframe correctly despite an invalid value, relying on error recovery behavior is fragile and may not work consistently across all browsers or future versions. Invalid attribute values can also cause unexpected results in automated tools, screen readers, and other user agents that parse HTML strictly. Writing valid, standards-compliant markup ensures predictable behavior everywhere.
Common causes of this error include:
- Adding CSS units to the attribute value (e.g., height="315px")
- Using percentages (e.g., height="100%")
- Decimal values (e.g., height="315.5")
- Whitespace before or after the number (e.g., height=" 315" or height="315 ")
- Copy-paste artifacts introducing hidden characters
If you need percentage-based or decimal sizing, use CSS instead of the HTML attribute. The height attribute only accepts whole pixel values.
Examples
❌ Invalid: Using px unit in the attribute
<iframe width="560" height="315px" src="https://example.com/video"></iframe>
The validator sees the p character after 315 and reports the error.
❌ Invalid: Using a percentage
<iframe width="100%" height="100%" src="https://example.com/video"></iframe>
Percentage values are not allowed in the height attribute.
❌ Invalid: Decimal value
<iframe width="560" height="315.5" src="https://example.com/video"></iframe>
The decimal point is not a digit, so the validator rejects it.
❌ Invalid: Leading or trailing whitespace
<iframe width="560" height=" 315 " src="https://example.com/video"></iframe>
The space before 315 is encountered where a digit is expected.
✅ Valid: Digits only
<iframe width="560" height="315" src="https://example.com/video"></iframe>
The value 315 contains only digits and is a valid non-negative integer.
✅ Valid: Using CSS for percentage-based sizing
If you need the iframe to scale responsively, remove the height attribute and use CSS instead:
<iframe
width="560"
height="315"
src="https://example.com/video"
style="width: 100%; height: 100%;"
></iframe>
Or better yet, apply styles through a stylesheet:
<style>
.responsive-iframe {
width: 100%;
height: 400px;
}
</style>
<iframe class="responsive-iframe" src="https://example.com/video"></iframe>
This keeps the HTML attributes valid while giving you full control over sizing through CSS, including support for units like %, vh, em, and calc().
In HTML, the name attribute on an <iframe> defines a browsing context name. This name can be referenced by other elements — for example, a link with target="my-frame" will open its URL inside the <iframe> whose name is "my-frame". The HTML specification reserves all browsing context names that start with an underscore for special keywords:
- _self — the current browsing context
- _blank — a new browsing context
- _parent — the parent browsing context
- _top — the topmost browsing context
Because the underscore prefix is reserved for these (and potentially future) keywords, the spec requires that any custom browsing context name must not begin with _. Setting name="_example" or name="_myFrame" on an <iframe> is invalid HTML, even though those exact strings aren’t currently defined keywords. Browsers may handle these inconsistently — some might ignore the name entirely, while others could treat it as one of the reserved keywords, leading to unexpected navigation behavior.
This matters for several reasons:
- Standards compliance: The WHATWG HTML living standard explicitly states that a valid browsing context name must not start with an underscore character.
- Predictable behavior: Using a reserved prefix can cause links or forms targeting that <iframe> to navigate in unintended ways (e.g., opening in a new tab instead of within the frame).
- Future-proofing: New underscore-prefixed keywords could be added to the spec, which might break pages that use custom names starting with _.
To fix the issue, simply rename the name attribute value so it doesn’t start with an underscore. You can use underscores elsewhere in the name — just not as the first character.
Examples
❌ Invalid: name starts with an underscore
<iframe src="https://example.com" name="_example"></iframe>
<a href="https://example.com/page" target="_example">Open in frame</a>
The name _example starts with an underscore, which makes it invalid. A browser might interpret _example unpredictably or ignore the name entirely when used as a target.
✅ Fixed: underscore removed from the start
<iframe src="https://example.com" name="example"></iframe>
<a href="https://example.com/page" target="example">Open in frame</a>
✅ Fixed: underscore used elsewhere in the name
<iframe src="https://example.com" name="my_example"></iframe>
<a href="https://example.com/page" target="my_example">Open in frame</a>
Underscores are perfectly fine as long as they aren’t the first character.
❌ Invalid: using a reserved keyword as a frame name
<iframe src="https://example.com" name="_blank"></iframe>
Using _blank as an <iframe> name is also invalid because it’s a reserved browsing context keyword. A link targeting _blank would open in a new window/tab rather than inside this <iframe>, which is almost certainly not what you intended.
✅ Fixed: descriptive name without underscore prefix
<iframe src="https://example.com" name="content-frame"></iframe>
<a href="https://example.com/page" target="content-frame">Open in frame</a>
How to fix
- Find every <iframe> element whose name attribute starts with _.
- Rename each one by removing the leading underscore or replacing it with a letter or other valid character.
- Update any target attributes on <a>, <form>, or <base> elements that reference the old name so they match the new name.
- Re-validate your HTML to confirm the issue is resolved.
The sandbox attribute is one of the most powerful security features available for <iframe> elements. When present with no value (or with specific tokens), it applies a strict set of restrictions to the embedded content — blocking scripts, form submissions, popups, same-origin access, and more. You selectively relax these restrictions by adding space-separated tokens like allow-scripts, allow-forms, or allow-popups.
The problem arises specifically when allow-scripts and allow-same-origin are used together. The allow-scripts token lets the embedded page execute JavaScript, while allow-same-origin lets it retain its original origin (rather than being treated as coming from a unique, opaque origin). With both enabled, the embedded page’s JavaScript can access the parent page’s DOM via window.parent (if they share the same origin) or, more critically, can use JavaScript to programmatically remove the sandbox attribute from its own <iframe> element. Once the attribute is removed, all sandboxing restrictions are lifted, making the sandbox attribute completely pointless.
This is why the W3C validator flags this combination — it’s not technically invalid HTML, but it’s a security anti-pattern that renders sandboxing ineffective. The WHATWG HTML specification explicitly warns against this combination for the same reason.
How to fix it
The fix depends on what the embedded content actually needs:
-
If the embedded page needs scripts but not same-origin access: Remove allow-same-origin. The page can still run JavaScript but will be treated as a unique origin, preventing it from accessing cookies, storage, or the parent frame’s DOM.
-
If the embedded page needs same-origin access but not scripts: Remove allow-scripts. The page retains its origin but cannot execute any JavaScript.
-
If you believe both are required: Reconsider whether sandboxing is the right approach. If the embedded content truly needs both script execution and same-origin access, the sandbox attribute provides no meaningful security benefit, and you may want to use other security mechanisms like Content-Security-Policy headers instead.
Always follow the principle of least privilege — only grant the permissions the embedded content strictly requires.
Examples
❌ Bad: using both allow-scripts and allow-same-origin
<iframe
src="https://example.com/widget"
sandbox="allow-scripts allow-same-origin">
</iframe>
This triggers the validator warning because the embedded page can use its script access combined with its preserved origin to break out of the sandbox entirely.
❌ Bad: both flags present alongside other tokens
<iframe
src="https://example.com/widget"
sandbox="allow-forms allow-scripts allow-same-origin allow-popups">
</iframe>
Even with additional tokens, the presence of both allow-scripts and allow-same-origin still defeats the sandbox.
✅ Good: allowing scripts without same-origin access
<iframe
src="https://example.com/widget"
sandbox="allow-scripts">
</iframe>
The embedded page can run JavaScript but is treated as a unique opaque origin, preventing it from accessing parent-page resources or removing its own sandbox.
✅ Good: allowing only the necessary permissions
<iframe
src="https://example.com/widget"
sandbox="allow-scripts allow-forms allow-popups">
</iframe>
This grants script execution, form submission, and popup capabilities while keeping the content sandboxed in a unique origin.
✅ Good: using an empty sandbox for maximum restriction
<iframe
src="https://example.com/widget"
sandbox="">
</iframe>
An empty sandbox attribute applies all restrictions — no scripts, no form submissions, no popups, no same-origin access, and more. This is the most secure option when the embedded content is purely static.
✅ Good: removing the sandbox when it provides no real benefit
<iframe
src="https://example.com/widget"
title="Example widget">
</iframe>
If you genuinely need both script execution and same-origin access, it’s more honest to omit sandbox entirely and rely on other security measures, rather than including an attribute that provides a false sense of security.
The URL in the src attribute value for an iframe is invalid as it contains an unexpected hash (#) character.
There’s an unexpected, possibly duplicate, hash character in the URÑ.
Examples:
Incorrect:
<iframe src="https://example.com/#?secret=123#abc"></iframe>
Correct (using only the query string):
<iframe src="https://example.com/#?secret=123"></iframe>
Correct (using the query string and a hash fragment) :
<iframe src="https://example.com/?secret=123#abc"></iframe>
When the W3C HTML Validator reports “Bad value X for attribute src on element iframe: Illegal character in query: [ is not allowed”, it means your URL contains unencoded square brackets in the query portion (everything after the ?). While most modern browsers will silently handle these characters and load the resource anyway, the URL does not conform to the URI syntax defined in RFC 3986, which the HTML specification requires.
According to RFC 3986, square brackets are reserved characters that have a specific purpose: they are only permitted in the host component of a URI to denote IPv6 addresses (e.g., http://[::1]/). Anywhere else in the URI — including the query string — they must be percent-encoded. The HTML living standard (WHATWG) requires that URLs in attributes like src, href, and action be valid URLs, which means they must follow these encoding rules.
Why this matters
- Standards compliance: Invalid URLs cause W3C validation failures, which can indicate deeper issues in your markup.
- Interoperability: While mainstream browsers are forgiving, some HTTP clients, proxies, CDNs, or web application firewalls may reject or mangle URLs with unencoded square brackets.
- Consistent behavior: Percent-encoding reserved characters guarantees that the URL is interpreted the same way everywhere — in browsers, server logs, link checkers, and automated tools.
- Copy-paste reliability: When users or tools copy a URL from your HTML source, an already-encoded URL is less likely to break during transmission through email clients, messaging apps, or other systems.
How to fix it
Replace every occurrence of [ with %5B and ] with %5D within the query string of the URL. If you’re generating URLs server-side or in JavaScript, use the language’s built-in URL encoding functions rather than doing manual find-and-replace:
- JavaScript: encodeURIComponent() or the URL / URLSearchParams APIs
- PHP: urlencode() or http_build_query()
- Python: urllib.parse.urlencode() or urllib.parse.quote()
These functions will automatically encode square brackets and any other reserved characters.
Examples
❌ Invalid — unencoded square brackets in query string
<iframe src="https://example.com/embed?filter[status]=active&filter[type]=video"></iframe>
The validator flags [ and ] as illegal characters in the query component.
✅ Valid — square brackets percent-encoded
<iframe src="https://example.com/embed?filter%5Bstatus%5D=active&filter%5Btype%5D=video"></iframe>
Replacing [ with %5B and ] with %5D resolves the error. The server receives the exact same parameter values — most server-side frameworks (PHP, Rails, etc.) automatically decode percent-encoded characters before processing them.
❌ Invalid — square brackets in a timestamp parameter
<iframe src="https://example.com/report?time=[2024-01-01]"></iframe>
✅ Valid — timestamp parameter properly encoded
<iframe src="https://example.com/report?time=%5B2024-01-01%5D"></iframe>
Generating encoded URLs in JavaScript
If you’re setting the src dynamically, let the browser handle encoding for you:
<iframe id="report-frame"></iframe>
<script>
const url = new URL("https://example.com/embed");
url.searchParams.set("filter[status]", "active");
url.searchParams.set("filter[type]", "video");
document.getElementById("report-frame").src = url.toString();
</script>
The URLSearchParams API automatically percent-encodes the brackets, producing a valid URL in the src attribute.
A note on other elements
This same rule applies to any HTML attribute that accepts a URL — including href on <a> elements, action on <form> elements, and src on <script> or <img> elements. Whenever you place a URL in HTML, ensure all reserved characters in the query string are properly percent-encoded.
The <iframe> element embeds another HTML document within the current page, and its src attribute specifies the URL of the content to load. According to the URL Living Standard and RFC 3986, URLs have a strict set of allowed characters. The space character (U+0020) is not one of them — it must always be percent-encoded when it appears in any part of a URL.
When the W3C HTML Validator encounters a literal space in the query portion of an <iframe>‘s src attribute, it raises this error because the browser has to guess what you meant. While most modern browsers will silently encode the space for you, relying on this behavior is problematic for several reasons:
- Standards compliance: The HTML specification requires that the src attribute contain a valid URL. A URL with literal spaces is not valid.
- Interoperability: Different browsers, HTTP clients, and intermediary servers may handle unencoded spaces differently. Some might truncate the URL at the first space, while others might encode it. This inconsistency can lead to broken embeds.
- Copy-paste and sharing: If a user or script extracts the URL from the HTML source, the unencoded space may cause errors in contexts that don’t perform automatic encoding.
To fix this, replace every literal space in the URL with %20. This is the standard percent-encoding for the space character. In query strings, you can also use + as an alternative encoding for spaces (this is common in application/x-www-form-urlencoded format), though %20 is universally accepted in all parts of a URL.
If you’re generating <iframe> URLs dynamically with JavaScript, use the built-in encodeURIComponent() function to encode individual query parameter values, or use the URL and URLSearchParams APIs, which handle encoding automatically.
Examples
❌ Invalid: literal spaces in the query string
<iframe src="https://maps.google.com/maps?q=2700 6th Avenue"></iframe>
The space between 2700 and 6th and between 6th and Avenue triggers the validation error.
✅ Fixed: spaces encoded as %20
<iframe src="https://maps.google.com/maps?q=2700%206th%20Avenue"></iframe>
✅ Fixed: spaces encoded as + in the query string
<iframe src="https://maps.google.com/maps?q=2700+6th+Avenue"></iframe>
Both %20 and + are valid encodings for spaces in query strings.
❌ Invalid: spaces in multiple query parameters
<iframe
src="https://example.com/embed?title=My Page&city=New York">
</iframe>
✅ Fixed: all spaces properly encoded
<iframe
src="https://example.com/embed?title=My%20Page&city=New%20York">
</iframe>
Using JavaScript to build encoded URLs
If you construct iframe URLs dynamically, let the browser handle encoding for you:
const url = new URL("https://maps.google.com/maps");
url.searchParams.set("q", "2700 6th Avenue");
const iframe = document.createElement("iframe");
iframe.src = url.toString();
// Result: "https://maps.google.com/maps?q=2700+6th+Avenue"
The URLSearchParams API automatically encodes spaces (as +), along with any other special characters, ensuring the resulting URL is always valid.
According to the HTML specification, the width and height attributes on <iframe> elements accept only a valid non-negative integer — a string of one or more ASCII digits (0–9) with no decimal points, spaces, or unit suffixes like px. This is different from CSS, where properties like width and height accept decimal values and units. The HTML attributes represent dimensions in CSS pixels implicitly, so only bare whole numbers are allowed.
When the W3C validator reports “Expected a digit but saw ‘.’ instead”, it means it was parsing the attribute value character by character and encountered a period (.) where only digits are valid. This typically happens when authors copy computed or fractional values from design tools, JavaScript calculations, or CSS into HTML attributes.
Why this matters
- Standards compliance: Browsers may handle invalid attribute values inconsistently. While most modern browsers will parse and truncate decimal values gracefully, the behavior is not guaranteed and falls outside the specification.
- Predictable rendering: Relying on how browsers handle malformed values can lead to subtle differences across browser engines. Using valid integers ensures consistent behavior everywhere.
- Code quality: Clean, valid markup is easier to maintain and signals professionalism, which matters especially for shared codebases and collaborative projects.
How to fix it
- Round the value to the nearest whole number. Use standard rounding rules: round up if the decimal portion is .5 or greater, round down otherwise.
- Remove any decimal point and trailing digits from the attribute value.
- If you need precise, fractional dimensions, use CSS instead of HTML attributes. CSS width and height properties accept decimal values with units (e.g., 602.88px).
Examples
❌ Invalid: decimal values in width and height
<iframe src="example.html" height="602.88" width="800.2"></iframe>
The validator will flag both attributes because 602.88 and 800.2 contain a . character.
✅ Fixed: whole number values
<iframe src="example.html" height="603" width="800"></iframe>
The decimal values have been rounded to the nearest integer: 602.88 becomes 603, and 800.2 becomes 800.
✅ Alternative: use CSS for precise dimensions
If you need exact fractional dimensions, move the sizing to CSS and remove the HTML attributes entirely:
<iframe src="example.html" style="height: 602.88px; width: 800.2px;"></iframe>
Or, better yet, use an external stylesheet:
<iframe src="example.html" class="content-frame"></iframe>
.content-frame {
width: 800.2px;
height: 602.88px;
}
❌ Invalid: other non-digit characters
This error can also appear if you include units in the attribute value:
<iframe src="example.html" width="800px" height="600px"></iframe>
✅ Fixed: remove the units
<iframe src="example.html" width="800" height="600"></iframe>
The same rule applies to the <img>, <video>, <canvas>, and other elements that accept width and height as HTML attributes — they all expect valid non-negative integers without decimals or units.
The HTML specification defines the width and height attributes on <iframe> as accepting only valid non-negative integers. These values are interpreted as pixel dimensions. Unlike some older HTML practices where percentage values were sometimes accepted by browsers, the current standard does not permit the % character in these attributes. When the W3C validator encounters a value like "100%", it expects every character to be a digit and flags the % as invalid.
This is a standards compliance issue, but it also affects predictability across browsers. While most modern browsers may still interpret width="100%" on an <iframe> as you’d expect, this behavior is non-standard and not guaranteed. Relying on it means your layout could break in certain browsers or rendering modes. Using CSS for percentage-based sizing is the correct, reliable approach.
How to Fix It
If you need a fixed pixel width, simply provide the integer value without any unit:
<iframe src="page.html" width="600" height="400"></iframe>
If you need a percentage-based width (e.g., to make the iframe responsive), remove the width attribute entirely and use CSS instead. You can apply styles inline or through a stylesheet.
Inline style approach:
<iframe src="page.html" style="width: 100%; height: 400px;"></iframe>
CSS class approach:
<iframe src="page.html" class="responsive-iframe"></iframe>
.responsive-iframe {
width: 100%;
height: 400px;
}
This same rule applies to the height attribute — values like height="50%" are equally invalid and should be handled through CSS.
Examples
❌ Invalid: Percentage in width attribute
<iframe src="https://example.com" width="100%" height="300"></iframe>
This triggers the error because 100% is not a valid non-negative integer.
❌ Invalid: Percentage in both width and height
<iframe src="https://example.com" width="100%" height="50%"></iframe>
Both attributes contain invalid values due to the % character.
✅ Valid: Fixed pixel values using attributes
<iframe src="https://example.com" width="800" height="300"></iframe>
Both values are valid non-negative integers representing pixels.
✅ Valid: Percentage sizing using CSS
<iframe src="https://example.com" style="width: 100%; height: 300px;"></iframe>
The percentage is handled by CSS, and no invalid attributes are present.
✅ Valid: Responsive iframe with a wrapper
For a fully responsive iframe that maintains an aspect ratio, a common pattern uses a wrapper element:
<div style="position: relative; width: 100%; aspect-ratio: 16 / 9;">
<iframe
src="https://example.com"
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;">
</iframe>
</div>
This approach keeps the HTML valid while giving you full control over the iframe’s responsive behavior through CSS.
The <iframe> element is designed to embed another browsing context (essentially a nested document) within the current page. According to the HTML specification, the content model of <iframe> is nothing — it must not contain any text nodes or child elements. Any text placed directly inside the <iframe> tags is invalid HTML.
Historically, some older HTML versions allowed fallback text inside <iframe> for browsers that didn’t support iframes. This is no longer the case in modern HTML. All current browsers support <iframe>, so there is no need for fallback content. The W3C validator flags this as an error because text or elements between the <iframe> tags violate the current HTML specification.
There are two valid ways to specify the content an iframe should display:
- src attribute — Provide a URL pointing to the document you want to embed.
- srcdoc attribute — Provide inline HTML content directly as the attribute’s value. The HTML is written as a string with appropriate character escaping (e.g., < for < and " for " if using double-quoted attributes).
If you were using text inside <iframe> as a description or fallback, consider using the title attribute instead to provide an accessible label for the embedded content.
Examples
❌ Invalid: text inside the <iframe> element
<iframe>Some content here</iframe>
<iframe><p>This is my embedded content.</p></iframe>
Both of these will trigger the “Text not allowed in element iframe in this context” error.
✅ Valid: using the src attribute
<iframe src="https://example.com" title="Example website"></iframe>
This loads the document at the specified URL inside the iframe.
✅ Valid: using the srcdoc attribute
<iframe srcdoc="<p>This is my embedded content.</p>" title="Inline content"></iframe>
This renders the inline HTML provided in the srcdoc attribute. Note that HTML special characters must be escaped when the attribute value is enclosed in double quotes.
✅ Valid: self-closing style (empty element)
<iframe src="https://example.com" title="Example website"></iframe>
The <iframe> element requires a closing tag, but nothing should appear between the opening and closing tags. Keeping the element empty is the correct approach.
✅ Valid: adding an accessible label with title
If your original intent was to describe the iframe’s content for users or assistive technologies, use the title attribute:
<iframe src="/embedded-report.html" title="Monthly sales report"></iframe>
The title attribute provides a label that screen readers can announce, improving the accessibility of the embedded content.
The <iframe> element embeds an entirely separate HTML document within the current page, creating its own independent browsing context. The <a> element, on the other hand, is an interactive element designed to navigate users to a new URL or location. When you nest an <iframe> inside an <a>, browsers face a conflict: user interactions like clicks could be intended for the embedded content inside the iframe or for the link itself. The HTML specification resolves this ambiguity by simply disallowing it.
According to the WHATWG HTML living standard, the <a> element’s content model does not permit interactive content as descendants. The <iframe> element is categorized as interactive content, which means it must not appear anywhere inside an <a> tag — not as a direct child and not nested deeper within other elements inside the link.
This restriction matters for several reasons:
- Accessibility: Screen readers and assistive technologies cannot reliably convey the purpose of a link that contains an embedded document. Users may not understand whether they are interacting with the link or the iframe.
- Unpredictable behavior: Different browsers may handle clicks on the iframe-inside-a-link differently, leading to inconsistent user experiences.
- Standards compliance: Violating the content model makes your HTML invalid, which can cause unexpected rendering and behavior.
To fix the issue, restructure your markup so the <iframe> and <a> are siblings or otherwise separated. If your goal is to provide a link alongside embedded content, place the link before or after the iframe. If you want a clickable preview that links somewhere, consider using an image thumbnail inside the link instead of an iframe.
Examples
❌ Invalid: <iframe> inside an <a> element
<a href="https://example.com">
<iframe src="https://example.com/embed"></iframe>
</a>
This triggers the validation error because the <iframe> is a descendant of the <a> element.
❌ Invalid: <iframe> nested deeper inside an <a> element
<a href="https://example.com">
<div>
<iframe src="https://example.com/embed"></iframe>
</div>
</a>
Even though the <iframe> is not a direct child, it is still a descendant of the <a> element, which is not allowed.
✅ Valid: Separate the <iframe> and <a> elements
<a href="https://example.com">Visit Example.com</a>
<iframe src="https://example.com/embed"></iframe>
The link and the iframe are siblings, so there is no nesting conflict.
✅ Valid: Use an image as a clickable preview instead
If the intent is to create a clickable preview that links to a page, use a thumbnail image rather than an iframe:
<a href="https://example.com">
<img src="preview-thumbnail.jpg" alt="Preview of Example.com">
</a>
✅ Valid: Wrap in a container with a separate link
If you need both an iframe and a related link displayed together, use a wrapper element:
<div class="embed-container">
<iframe src="https://example.com/embed" title="Embedded content from Example.com"></iframe>
<p><a href="https://example.com">Open Example.com in a new page</a></p>
</div>
Note that when using <iframe>, it’s good practice to include a title attribute to describe the embedded content for accessibility purposes.
The longdesc attribute was originally designed to point to a URL containing a detailed description of an element’s content, primarily as an accessibility feature for screen reader users. On <iframe> elements, it was intended to describe the framed content for users who couldn’t perceive it directly. However, the attribute was poorly implemented across browsers, rarely used by assistive technologies, and was ultimately removed from the HTML specification for <iframe> elements.
Using obsolete attributes causes W3C validation errors and can create a false sense of accessibility. Since browsers and assistive technologies don’t reliably process longdesc on iframes, users who need the description may never actually reach it. A visible link, on the other hand, is universally accessible — it works for all users regardless of their browser, device, or assistive technology.
The fix is straightforward: remove the longdesc attribute from the <iframe> and place a regular <a> element near the iframe that links to the description page. This approach is more robust because the link is visible, discoverable, and works everywhere.
Examples
❌ Obsolete: Using longdesc on an <iframe>
<iframe src="report.html" title="Annual report" longdesc="report-description.html"></iframe>
This triggers the validation error because longdesc is no longer a valid attribute on <iframe>.
✅ Fixed: Using a visible link instead
<iframe src="report.html" title="Annual report"></iframe>
<p>
<a href="report-description.html">Read a detailed description of the annual report</a>
</p>
The longdesc attribute is removed, and a descriptive link is placed adjacent to the iframe so all users can access it.
✅ Alternative: Wrapping in a <figure> for better semantics
For a more structured approach, you can use a <figure> element with a <figcaption> to associate the description link with the iframe:
<figure>
<iframe src="report.html" title="Annual report"></iframe>
<figcaption>
Annual report overview.
<a href="report-description.html">Read the full description</a>.
</figcaption>
</figure>
This groups the iframe and its caption together semantically, making the relationship between them clear to both sighted users and assistive technologies.
✅ Alternative: Using aria-describedby for additional context
If you want to provide a short inline description that assistive technologies can announce automatically, you can use aria-describedby:
<p id="report-desc">
This iframe contains the interactive annual report for 2024.
<a href="report-description.html">Read the full description</a>.
</p>
<iframe src="report.html" title="Annual report" aria-describedby="report-desc"></iframe>
This links the iframe to the descriptive paragraph programmatically. Screen readers will announce the content of the referenced element when the iframe receives focus, while the visible link remains available to all users.
Key Takeaways
- Always include a title attribute on <iframe> elements to describe their purpose — this is an important baseline accessibility practice.
- Replace longdesc with a visible <a> element that links to the long description.
- Place the link near the iframe so users can easily find it.
- Consider using <figure> and <figcaption> or aria-describedby for stronger semantic association between the iframe and its description.
Ready to validate your sites?
Start your free trial today.