HTML Guides
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 <pattern> element lives inside <svg>, and SVG is an XML-based language. Unlike regular HTML — where id values follow relatively relaxed rules — SVG content must comply with XML 1.0 naming conventions. This means id values have stricter character and formatting requirements than you might be used to in plain HTML.
XML 1.0 Name Rules
An XML 1.0 name (used for id attributes in SVG) must follow these rules:
- First character must be a letter (
A–Z,a–z) or an underscore (_). - Subsequent characters can be letters, digits (
0–9), hyphens (-), underscores (_), or periods (.). - Spaces and special characters like
!,@,#,$,%,(,), etc. are not allowed anywhere in the name.
Common mistakes that trigger this error include starting an id with a digit (e.g., 1pattern), a hyphen (e.g., -myPattern), or a period (e.g., .dotPattern), or including characters like spaces or colons.
Why This Matters
- Standards compliance: SVG is parsed as XML in many contexts. An invalid XML name can cause parsing errors or unexpected behavior, especially when SVG is served with an XML MIME type or embedded in XHTML.
- Functionality: The
<pattern>element'sidis typically referenced viaurl(#id)infillorstrokeattributes. An invalididmay cause the pattern reference to silently fail, leaving elements unfilled or invisible. - Cross-browser consistency: While some browsers are lenient with invalid XML names, others are not. Using valid names ensures consistent rendering across all browsers and environments.
How to Fix
Rename the id value so it starts with a letter or underscore and contains only valid characters. If you reference this id elsewhere (e.g., in fill="url(#...)" or in CSS), update those references to match.
Examples
❌ Invalid: id starts with a digit
<svgwidth="200"height="200"xmlns="http://www.w3.org/2000/svg">
<defs>
<patternid="1stPattern"width="10"height="10"patternUnits="userSpaceOnUse">
<circlecx="5"cy="5"r="3"fill="blue"/>
</pattern>
</defs>
<rectwidth="200"height="200"fill="url(#1stPattern)"/>
</svg>
❌ Invalid: id starts with a hyphen
<svgwidth="200"height="200"xmlns="http://www.w3.org/2000/svg">
<defs>
<patternid="-stripe-bg"width="10"height="10"patternUnits="userSpaceOnUse">
<rectwidth="5"height="10"fill="red"/>
</pattern>
</defs>
<rectwidth="200"height="200"fill="url(#-stripe-bg)"/>
</svg>
❌ Invalid: id contains special characters
<svgwidth="200"height="200"xmlns="http://www.w3.org/2000/svg">
<defs>
<patternid="my pattern!"width="10"height="10"patternUnits="userSpaceOnUse">
<circlecx="5"cy="5"r="3"fill="green"/>
</pattern>
</defs>
<rectwidth="200"height="200"fill="url(#my pattern!)"/>
</svg>
✅ Valid: id starts with a letter
<svgwidth="200"height="200"xmlns="http://www.w3.org/2000/svg">
<defs>
<patternid="firstPattern"width="10"height="10"patternUnits="userSpaceOnUse">
<circlecx="5"cy="5"r="3"fill="blue"/>
</pattern>
</defs>
<rectwidth="200"height="200"fill="url(#firstPattern)"/>
</svg>
✅ Valid: id starts with an underscore
<svgwidth="200"height="200"xmlns="http://www.w3.org/2000/svg">
<defs>
<patternid="_stripe-bg"width="10"height="10"patternUnits="userSpaceOnUse">
<rectwidth="5"height="10"fill="red"/>
</pattern>
</defs>
<rectwidth="200"height="200"fill="url(#_stripe-bg)"/>
</svg>
✅ Valid: Using letters, digits, hyphens, and underscores
<svgwidth="200"height="200"xmlns="http://www.w3.org/2000/svg">
<defs>
<patternid="dot-grid_v2"width="10"height="10"patternUnits="userSpaceOnUse">
<circlecx="5"cy="5"r="3"fill="green"/>
</pattern>
</defs>
<rectwidth="200"height="200"fill="url(#dot-grid_v2)"/>
</svg>
Note that this same XML 1.0 naming rule applies to id attributes on all SVG elements — not just <pattern>. If you see similar errors on elements like <linearGradient>, <clipPath>, or <filter>, the same fix applies: ensure the id starts with a letter or underscore and uses only valid characters.
The integrity attribute enables Subresource Integrity (SRI), a security feature that lets browsers verify that a fetched resource (such as a JavaScript file from a CDN) has not been tampered with. The attribute value follows the format [algorithm]-[base64-encoded hash], where the algorithm is typically sha256, sha384, or sha512, and the hash is a base64 representation of the file's cryptographic digest.
Base64 encoding works by converting binary data into a string of ASCII characters drawn from a 64-character alphabet. A key property of valid base64 is that the encoded output must always have a length that is a multiple of 4. When the raw binary data doesn't divide evenly, the output is padded with one or two = characters to reach the correct length. If the base64 string in your integrity attribute has an incorrect length — for instance, it was truncated, manually edited, or copied incompletely — the validator will flag it as invalid.
This matters for several reasons:
- Security: If the
integrityvalue is malformed, the browser cannot verify the resource. Depending on the browser, it may block the script entirely, breaking your site's functionality. - Standards compliance: The HTML specification requires the hash portion to be a valid base64 string. An invalid value is a conformance error.
- Reliability: A malformed hash will never match any file, so the SRI check will always fail, effectively making the script unusable.
Common causes of this error include:
- Copying the hash value incompletely (missing trailing
=padding or other characters). - Manually modifying the hash string.
- Using a tool that produced an incorrectly encoded output.
- Mixing up base64 and base64url encodings (base64url uses
-and_instead of+and/, and often omits padding).
To fix the issue, regenerate the correct SRI hash for the exact file being referenced. You can do this with the command line:
openssl dgst -sha384 -binary script.js | openssl base64 -A
Or using shasum and base64:
shasum -b -a 384 script.js | awk '{print $1}' | xxd -r -p | base64
Online tools like the SRI Hash Generator can also produce the correct value. After generating the hash, prepend the algorithm prefix (e.g., sha384-) and verify that the base64 portion has a length divisible by 4.
Examples
Incorrect: Malformed base64 value
The hash below is not a valid base64 string — its length is not a multiple of 4, and it contains the character !, which is not in the base64 alphabet.
<script
src="https://cdn.example.com/library.js"
integrity="sha384-BadBase64Value!"
crossorigin="anonymous"></script>
Incorrect: Truncated hash missing padding
This hash has been accidentally truncated, losing the trailing = padding characters that make it a valid base64 string.
<script
src="https://cdn.example.com/library.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9Gh8S7f1bE0q/PuF3LtHac+obYTK2B69B1a8t"
crossorigin="anonymous"></script>
Correct: Properly formatted SRI hash
The base64 hash is the correct length (a multiple of 4) and uses only valid base64 characters. The trailing T completes the final 4-character group without needing padding in this case, but other hashes may end with = or ==.
<script
src="https://cdn.example.com/library.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9Gh8S7f1bE0q/PuF3LtHac+obYTK2B69B1a8tT"
crossorigin="anonymous"></script>
Correct: Multiple hashes for fallback
You can provide multiple hash values separated by spaces. Each one must be independently valid base64 with the correct length.
<script
src="https://cdn.example.com/library.js"
integrity="sha256-BpfGp+xz0kN9mMKoECQbGRab7fMi/dsRZ7Sy7LVd3CE= sha384-oqVuAfXRKap7fdgcCY5uykM6+R9Gh8S7f1bE0q/PuF3LtHac+obYTK2B69B1a8tT"
crossorigin="anonymous"></script>
Always ensure the hash is generated from the exact file the src attribute points to. If the CDN serves a different version or a minified variant, the hash will not match, and the browser will block the resource regardless of whether the base64 encoding is valid.
The lang attribute on the <html> element tells browsers, screen readers, and search engines what language the page content is written in. Its value must follow the BCP 47 standard, which uses ISO 639 language codes as the primary language subtag. When the validator reports that a language subtag "is not a valid ISO language part of a language tag," it means the code you provided doesn't match any recognized language in the ISO 639 registry.
Common Causes
Typos in the language code
A simple misspelling like emg instead of en, or fre instead of fr, will trigger this error.
Using a country code instead of a language code
Country codes (ISO 3166) and language codes (ISO 639) are different standards. For example, uk is the country code for the United Kingdom, but it's also the valid language code for Ukrainian. Using gb (Great Britain) as a language would be invalid. Similarly, us is not a language code — you need en for English or en-US for American English specifically.
Using made-up or deprecated codes
Codes like xx, en-UK, or other non-standard values will fail validation. Note that while en-US and en-GB are valid (language-region format), en-UK is not because UK is not the correct ISO 3166-1 region subtag for the United Kingdom — GB is.
Why This Matters
- Accessibility: Screen readers rely on the
langattribute to select the correct pronunciation rules and voice profile. An invalid language code can cause assistive technology to mispronounce content or fall back to a default language. - SEO: Search engines use the
langattribute as a signal for serving the right content to users in the appropriate language and region. - Browser behavior: Browsers use the language tag for spell-checking, hyphenation, font selection, and other language-sensitive rendering decisions.
How to Fix It
- Identify the language your page is written in.
- Look up the correct ISO 639-1 two-letter code (preferred) or ISO 639-2 three-letter code for that language.
- If you need to specify a regional variant, append a hyphen and the ISO 3166-1 region code (e.g.,
pt-BRfor Brazilian Portuguese). - Replace the invalid value in the
langattribute.
Some commonly used valid language codes:
| Language | Code |
|---|---|
| English | en |
| English (US) | en-US |
| English (UK) | en-GB |
| Spanish | es |
| French | fr |
| German | de |
| Portuguese (Brazil) | pt-BR |
| Chinese (Simplified) | zh-Hans |
| Japanese | ja |
| Arabic | ar |
Examples
❌ Invalid: Typo in language code
<htmllang="emg">
❌ Invalid: Country code used instead of language code
<htmllang="us">
❌ Invalid: Incorrect region subtag
<htmllang="en-UK">
❌ Invalid: Made-up language code
<htmllang="english">
✅ Valid: Correct two-letter language code
<htmllang="en">
✅ Valid: Language with region subtag
<htmllang="en-GB">
✅ Valid: Full document with proper lang attribute
<!DOCTYPE html>
<htmllang="fr">
<head>
<metacharset="utf-8">
<title>Ma page</title>
</head>
<body>
<p>Bonjour le monde !</p>
</body>
</html>
You can verify your language tag using the IANA Language Subtag Registry or the BCP 47 Language Subtag Lookup tool to ensure your code is valid before updating your markup.
The loading attribute tells the browser how to prioritize loading an image. Setting it to "lazy" defers loading until the image is near the viewport, which can improve page performance by reducing initial load time and saving bandwidth. Setting it to "eager" instructs the browser to load the image immediately, regardless of its position on the page. When the attribute is omitted, the browser defaults to "eager" behavior.
Any value other than these two exact lowercase strings is invalid according to the HTML specification. Common mistakes include using numeric values like "1" or "0", boolean-style values like "true" or "false", uppercase variants like "Lazy", or misspellings like "laxy". When a browser encounters an invalid value, it ignores the attribute and falls back to the default eager loading behavior. While this means the page won't break visually, the intended lazy-loading optimization won't take effect, and the HTML will fail W3C validation.
This matters for a few reasons:
- Performance: If you intended to use
"lazy"but provided an invalid value, your images will all load eagerly, defeating the purpose and potentially degrading page speed. - Standards compliance: Invalid attribute values signal potential bugs in your markup and can cause issues with tools and parsers that expect well-formed HTML.
- Maintainability: Valid, predictable markup is easier to maintain and debug across teams and over time.
To fix the issue, check the loading attribute value and replace it with one of the two valid options. If you want deferred loading, use "lazy". If you want immediate loading (or don't need to specify), use "eager" or simply remove the attribute.
Examples
Invalid values
Each of these will trigger the validation error:
<!-- Numeric value -->
<imgsrc="photo.jpg"alt="A sunset over the ocean"loading="1">
<!-- Boolean-style value -->
<imgsrc="photo.jpg"alt="A sunset over the ocean"loading="true">
<!-- Misspelled value -->
<imgsrc="photo.jpg"alt="A sunset over the ocean"loading="laxy">
<!-- Wrong case -->
<imgsrc="photo.jpg"alt="A sunset over the ocean"loading="Lazy">
Correct: lazy loading
Use "lazy" for images that are below the fold or not immediately visible to the user:
<imgsrc="photo.jpg"alt="A sunset over the ocean"loading="lazy">
Correct: eager loading
Use "eager" for critical images that should load right away, such as hero images or logos:
<imgsrc="logo.png"alt="Company logo"loading="eager">
Correct: omitting the attribute
Since "eager" is the default behavior, you can simply remove the attribute if you want immediate loading:
<imgsrc="logo.png"alt="Company logo">
Practical usage in context
A common and effective pattern is to eagerly load above-the-fold images while lazy-loading everything else:
<!-- Hero image: load immediately -->
<imgsrc="hero-banner.jpg"alt="Welcome to our store"loading="eager">
<!-- Product images further down the page: defer loading -->
<imgsrc="product-1.jpg"alt="Running shoes"loading="lazy">
<imgsrc="product-2.jpg"alt="Hiking boots"loading="lazy">
<imgsrc="product-3.jpg"alt="Sandals"loading="lazy">
Note that the loading attribute also works on iframe elements with the same two valid values: "lazy" and "eager".
The media attribute tells the browser under which conditions a linked stylesheet (or other resource) should be applied. Its value must be a valid media query list as defined by the CSS Media Queries specification. When the W3C HTML Validator reports a parse error for this attribute, it means the value couldn't be parsed as a valid media query. The browser may ignore the attribute entirely or behave unpredictably, potentially loading the stylesheet in all conditions or not at all.
Common causes of this error include:
- Typos in media types or features — e.g.,
scrreninstead ofscreen, ormax-widhtinstead ofmax-width. - Missing parentheses around media features — e.g.,
max-width: 600pxinstead of(max-width: 600px). - Invalid or stray characters — e.g., extra commas, semicolons, or unmatched parentheses.
- Using CSS syntax instead of media query syntax — e.g., writing
@media screenor including curly braces. - Incorrect logical operators — e.g., using
orat the top level instead of a comma, or misspellingnotoronly. - Server-side template placeholders left unresolved — e.g.,
media="{{mediaQuery}}"rendered literally.
Getting this right matters for standards compliance and predictable cross-browser behavior. Browsers are generally lenient and may try to recover from malformed media queries, but the recovery behavior isn't guaranteed to be consistent. A broken media attribute can cause stylesheets to load when they shouldn't (wasting bandwidth) or not load when they should (breaking layout). It also affects performance, since browsers use the media attribute to prioritize resource loading.
To fix the issue, verify that your media value is a syntactically correct media query. Use valid media types (screen, print, all), wrap media features in parentheses, and separate multiple queries with commas.
Examples
Incorrect: missing parentheses around media feature
<!-- ❌ Parse error: media features must be wrapped in parentheses -->
<linkrel="stylesheet"href="responsive.css"media="max-width: 600px">
Correct: parentheses around media feature
<linkrel="stylesheet"href="responsive.css"media="(max-width: 600px)">
Incorrect: typo in media type
<!-- ❌ Parse error: "scrren" is not a valid media type -->
<linkrel="stylesheet"href="styles.css"media="scrren">
Correct: valid media type
<linkrel="stylesheet"href="styles.css"media="screen">
Incorrect: using or instead of a comma to separate queries
<!-- ❌ Parse error: top-level "or" is not valid between media queries -->
<linkrel="stylesheet"href="styles.css"media="screen or print">
Correct: comma-separated media query list
<linkrel="stylesheet"href="styles.css"media="screen, print">
Incorrect: stray semicolon in the value
<!-- ❌ Parse error: semicolons are not valid in media queries -->
<linkrel="stylesheet"href="styles.css"media="screen; (max-width: 768px)">
Correct: combining media type and feature with and
<linkrel="stylesheet"href="styles.css"media="screen and (max-width: 768px)">
More valid media query examples
<!-- Simple media type -->
<linkrel="stylesheet"href="print.css"media="print">
<!-- Apply to all media (default behavior, but explicit) -->
<linkrel="stylesheet"href="base.css"media="all">
<!-- Multiple conditions with "and" -->
<linkrel="stylesheet"href="tablet.css"media="screen and (min-width: 768px) and (max-width: 1024px)">
<!-- Using "not" to exclude a media type -->
<linkrel="stylesheet"href="no-print.css"media="not print">
<!-- Multiple queries separated by commas (logical OR) -->
<linkrel="stylesheet"href="special.css"media="(max-width: 600px), (orientation: portrait)">
<!-- Prefers-color-scheme for dark mode -->
<linkrel="stylesheet"href="dark.css"media="(prefers-color-scheme: dark)">
If you're unsure whether your media query is valid, try testing it in a browser's developer tools by adding a <style> block with @media and the same query — if the browser highlights it as invalid, the same value will fail in the media attribute.
The media attribute on a <link> element specifies which media or device types the linked resource (typically a stylesheet) is designed for. It accepts a valid media query or a comma-separated list of media types. In the current CSS specification (Media Queries Level 4), only three media types remain valid: all, screen, and print. All other media types from older specifications — including projection, handheld, tv, tty, braille, embossed, and aural — have been deprecated.
The projection media type was originally intended to target presentation and projector-based displays. In practice, browser support for it was extremely limited (only Opera had meaningful support), and the use case never gained traction. The CSS Working Group deprecated it because the distinction between a "screen" and a "projection" display is no longer meaningful — modern browsers treat projectors, external displays, and monitors uniformly under the screen type.
Why this matters
- Standards compliance: Using deprecated media types causes W3C validation errors, which can signal broader code quality issues.
- No practical effect: Modern browsers simply ignore unrecognized media types. If
projectionis the only value in yourmediaattribute, the stylesheet may not load at all in some browsers. If it appears alongsidescreen, the browser loads the stylesheet based on thescreenmatch and silently discardsprojection— meaning the deprecated value adds nothing. - Maintainability: Keeping deprecated values in your code can confuse other developers and create the false impression that the stylesheet has special behavior for projectors.
How to fix it
- Remove
projectionfrom themediaattribute value. - If
screenor another valid type was already listed alongsideprojection, keep the valid type. - If
projectionwas the only value, replace it withscreen(since projectors are treated as screens by modern browsers). - If the stylesheet should apply universally, remove the
mediaattribute entirely or set it toall.
Examples
Incorrect
Using the deprecated projection media type alongside screen:
<linkrel="stylesheet"href="style.css"media="screen, projection">
Using projection as the sole media type:
<linkrel="stylesheet"href="slides.css"media="projection">
Correct
Remove projection and keep the valid screen type:
<linkrel="stylesheet"href="style.css"media="screen">
Replace projection with screen, since projectors are handled as screens:
<linkrel="stylesheet"href="slides.css"media="screen">
Target both screens and print:
<linkrel="stylesheet"href="style.css"media="screen, print">
If the stylesheet should apply to all devices, omit the media attribute (which defaults to all):
<linkrel="stylesheet"href="style.css">
Or explicitly set it to all:
<linkrel="stylesheet"href="style.css"media="all">
Using media queries instead of media types
If you need more granular control over when a stylesheet applies — for example, targeting large displays commonly used for presentations — you can use a media query with feature conditions instead of relying on deprecated media types:
<linkrel="stylesheet"href="presentation.css"media="screen and (min-width: 1920px)">
This approach is standards-compliant and gives you far more precise targeting than the old media types ever provided.
The media attribute on a <link> element specifies the conditions under which the linked resource should apply. It accepts either a simple media type or a full media query. When the validator reports "unrecognized media," it means the value you provided doesn't match any known media type or valid media query syntax.
Several older media types that were defined in earlier CSS specifications have been deprecated. Types like handheld, projection, tv, tty, aural, braille, and embossed are no longer recognized as valid. Modern CSS and HTML only support three media types: all, screen, and print. If you're using a deprecated type, you should replace it with an appropriate modern media query that targets the device characteristics you need.
Beyond deprecated types, this error also occurs when a media query expression is malformed — for example, missing parentheses around a feature expression, using an unknown feature name, or having a typo in the value.
Why this matters
- Standards compliance: Using unrecognized media types means your HTML doesn't conform to the current HTML and CSS specifications.
- Browser behavior: Browsers may ignore the entire
<link>element or apply the resource unconditionally when they encounter an unrecognized media type, leading to unexpected results. - Performance: The
mediaattribute helps browsers prioritize resource loading. A valid media query allows the browser to defer loading stylesheets that don't match the current context (e.g., print stylesheets), improving page load performance.
How to fix it
- Replace deprecated media types with
screen,print, orall, or use modern media queries that target specific device features. - Check for typos in your media type or query expression.
- Validate your media query syntax — feature expressions must be wrapped in parentheses and use recognized feature names like
max-width,orientation, orprefers-color-scheme.
Examples
Incorrect: using a deprecated media type
<linkrel="stylesheet"href="mobile.css"media="handheld">
The handheld media type is deprecated and will trigger the validation error.
Incorrect: misspelled media type
<linkrel="stylesheet"href="styles.css"media="screen">
Incorrect: malformed media query
<linkrel="stylesheet"href="responsive.css"media="max-width: 768px">
The feature expression is missing its surrounding parentheses.
Correct: using valid media types
<linkrel="stylesheet"href="general.css">
<linkrel="stylesheet"href="print.css"media="print">
<linkrel="stylesheet"href="screen.css"media="screen">
When no media attribute is specified, it defaults to all.
Correct: replacing deprecated types with modern media queries
Instead of media="handheld", use a media query that targets small screens or specific device capabilities:
<linkrel="stylesheet"href="mobile.css"media="screen and (max-width: 768px)">
Correct: using complex media queries
<linkrel="stylesheet"href="dark.css"media="(prefers-color-scheme: dark)">
<linkrel="stylesheet"href="portrait.css"media="screen and (orientation: portrait)">
<linkrel="stylesheet"href="large.css"media="screen and (min-width: 1200px)">
Valid media types reference
| Media type | Description |
|---|---|
all | Matches all devices (default when omitted) |
print | Matches printers and print preview mode |
screen | Matches screens (computers, tablets, phones) |
For anything more specific than these three types, use media feature expressions like (max-width: 600px), (hover: hover), or (prefers-reduced-motion: reduce) to target the exact device characteristics you need.
The multiple attribute tells the browser that the user can supply more than one value for a given input. For <input type="file">, it allows selecting multiple files at once. For <input type="email">, it allows entering a comma-separated list of email addresses. These are the only two input types that support the multiple attribute according to the HTML specification.
This validation error can appear for two distinct reasons, and sometimes both at once:
An invalid value is assigned to the boolean attribute. Boolean attributes in HTML follow strict rules. Valid syntaxes are: the attribute name alone (
multiple), an empty string value (multiple=""), or the attribute's own name as the value (multiple="multiple"). Any other value — includingmultiple="true",multiple="1", ormultiple="yes"— is invalid.The attribute is used on an unsupported input type. Placing
multipleon input types liketext,number,password, orurlis not valid because those types don't define behavior for this attribute. Browsers will simply ignore it, but it still constitutes invalid markup.
Why this matters
- Standards compliance: Invalid boolean attribute values violate the WHATWG HTML specification. While most browsers are forgiving and may still interpret
multiple="true"as the attribute being present, relying on this behavior is fragile and non-standard. - Accessibility: Assistive technologies rely on valid, well-structured markup. An invalid attribute value could lead to unpredictable behavior in screen readers or other tools.
- Maintainability: Using
multipleon an unsupported input type suggests a misunderstanding of the element's capabilities, which can confuse other developers and lead to bugs.
How to fix it
- Remove the value entirely: Change
multiple="1"ormultiple="true"to justmultiple. - Use a valid boolean syntax if a value is required: Some templating systems or XML-based contexts (like XHTML) require explicit attribute values. In those cases, use
multiple=""ormultiple="multiple". - Ensure the input type supports
multiple: Onlytype="email"andtype="file"accept this attribute. If you need multi-value input for other types, consider alternative approaches like multiple separate inputs, a<select multiple>element, or a JavaScript-based solution.
Examples
Invalid: wrong value on a boolean attribute
<!-- Bad: "1" is not a valid boolean attribute value -->
<inputtype="file"name="attachments"multiple="1">
<!-- Bad: "true" is not a valid boolean attribute value -->
<inputtype="email"name="recipients"multiple="true">
Invalid: multiple on an unsupported input type
<!-- Bad: type="text" does not support the multiple attribute -->
<inputtype="text"name="tags"multiple>
<!-- Bad: type="number" does not support the multiple attribute -->
<inputtype="number"name="quantities"multiple>
Valid: correct usage of multiple
<!-- Correct: boolean attribute with no value -->
<inputtype="file"name="attachments"multiple>
<!-- Correct: empty string value (valid boolean syntax) -->
<inputtype="email"name="recipients"multiple="">
<!-- Correct: attribute name as value (valid for XHTML compatibility) -->
<inputtype="file"name="documents"multiple="multiple">
Full corrected document
<!DOCTYPE html>
<htmllang="en">
<head>
<title>Upload Form</title>
</head>
<body>
<formaction="/submit"method="post"enctype="multipart/form-data">
<labelfor="recipients">Recipients:</label>
<inputtype="email"id="recipients"name="recipients"multipleplaceholder="a@example.com, b@example.com">
<labelfor="files">Attachments:</label>
<inputtype="file"id="files"name="files"multiple>
<buttontype="submit">Send</button>
</form>
</body>
</html>
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
<iframesrc="https://example.com"name="_example"></iframe>
<ahref="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
<iframesrc="https://example.com"name="example"></iframe>
<ahref="https://example.com/page"target="example">Open in frame</a>
✅ Fixed: underscore used elsewhere in the name
<iframesrc="https://example.com"name="my_example"></iframe>
<ahref="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
<iframesrc="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
<iframesrc="https://example.com"name="content-frame"></iframe>
<ahref="https://example.com/page"target="content-frame">Open in frame</a>
How to fix
- Find every
<iframe>element whosenameattribute starts with_. - Rename each one by removing the leading underscore or replacing it with a letter or other valid character.
- Update any
targetattributes 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.
Understanding Boolean Attributes in HTML
In HTML, boolean attributes work differently from regular attributes. A boolean attribute's presence on an element represents true, and its absence represents false. According to the HTML specification, a boolean attribute may only have three valid forms:
- The attribute name alone:
novalidate - The attribute with an empty value:
novalidate="" - The attribute with a value matching its own name (case-insensitive):
novalidate="novalidate"
Any other value — such as "true", "false", "1", "0", or "yes" — is invalid and triggers this validation error. This is a common source of confusion, especially for developers coming from frameworks like React (which uses noValidate={true}) or from languages where boolean attributes accept explicit true/false strings.
Why This Matters
- Standards compliance: Using invalid values violates the HTML specification and will cause W3C validation failures.
- Unexpected behavior: While most browsers are lenient and treat any value of
novalidateas "present" (meaning evennovalidate="false"would disable validation, not enable it), relying on this behavior is unreliable and misleading to other developers reading your code. - Maintainability: Writing
novalidate="false"suggests the form should be validated, but the opposite is true — the attribute is present, so validation is skipped. This creates confusing, error-prone code.
About the novalidate Attribute
The novalidate attribute tells the browser to skip its built-in constraint validation when the form is submitted. Without it, the browser checks required fields, input patterns, email formats, and other constraints before allowing submission.
If novalidate is not set on the form, individual submit buttons can still bypass validation on a per-button basis using the formnovalidate attribute on a <button>, <input type="submit">, or <input type="image"> element.
Examples
❌ Invalid: Arbitrary value on novalidate
<formmethod="post"novalidate="true">
<label>Email:
<inputtype="email"name="email"required>
</label>
<button>Submit</button>
</form>
This triggers the error because "true" is not a valid value for a boolean attribute. Other invalid variations include:
<formmethod="post"novalidate="1">
<formmethod="post"novalidate="yes">
<formmethod="post"novalidate="false">
Note that novalidate="false" is especially dangerous — it does not enable validation. Because the attribute is present, the browser disables validation regardless of the value.
✅ Valid: Attribute name alone (recommended)
<formmethod="post"novalidate>
<label>Email:
<inputtype="email"name="email"required>
</label>
<button>Submit</button>
</form>
✅ Valid: Empty string value
<formmethod="post"novalidate="">
<label>Email:
<inputtype="email"name="email"required>
</label>
<button>Submit</button>
</form>
✅ Valid: Value matching the attribute name
<formmethod="post"novalidate="novalidate">
<label>Email:
<inputtype="email"name="email"required>
</label>
<button>Submit</button>
</form>
✅ Valid: Removing the attribute to enable validation
If you want the form to be validated, simply remove the novalidate attribute entirely:
<formmethod="post">
<label>Email:
<inputtype="email"name="email"required>
</label>
<button>Submit</button>
</form>
✅ Using formnovalidate on a specific button
If you want validation on the primary submit but want to skip it for a "Save Draft" button, use formnovalidate on the button instead:
<formmethod="post">
<label>Email:
<inputtype="email"name="email"required>
</label>
<button>Submit</button>
<buttonformnovalidate>Save Draft</button>
</form>
This pattern keeps validation active for the main submission while allowing drafts to bypass it — without needing novalidate on the form at all.
The ping attribute specifies a space-separated list of URLs that the browser should notify (via a small POST request) when a user follows a hyperlink. This is commonly used for click tracking and analytics. According to the HTML specification, every URL in the list must be a valid, non-empty URL that uses either the http or https scheme — no other schemes or relative paths are permitted.
This restriction exists for practical and security reasons. The ping mechanism is specifically designed for web-based tracking endpoints, so only web protocols make sense. Relative URLs are disallowed because the ping is sent as a separate request independent of normal navigation, and the specification requires absolute URLs to unambiguously identify the target server. Using invalid values won't produce the intended tracking behavior and will cause the browser to silently ignore the ping attribute entirely.
From an accessibility and standards compliance standpoint, ensuring valid ping values means your analytics will work reliably in browsers that support the attribute. Note that browser support varies — some browsers (notably Firefox) disable ping by default or hide it behind a preference — so you should not rely on it as your sole tracking mechanism.
How to fix it
- Replace relative URLs with absolute URLs. If you have a value like
/trackortrack.php, prepend the full origin (e.g.,https://example.com/track). - Remove non-HTTP schemes. Values like
mailto:someone@example.comorftp://example.com/logare not valid forping. - Ensure each URL in the list is properly formatted. Multiple URLs must be separated by spaces (not commas or semicolons), and each one must be a complete
httporhttpsURL.
Examples
Incorrect: relative URL
<ahref="https://example.com"ping="/track">Visit Example</a>
The value /track is a relative URL, which is not allowed in the ping attribute.
Incorrect: unsupported scheme
<ahref="https://example.com"ping="ftp://example.com/log">Visit Example</a>
The ftp: scheme is not permitted — only http and https are valid.
Incorrect: comma-separated URLs
<ahref="https://example.com"ping="https://example.com/track, https://analytics.example.com/log">Visit Example</a>
Multiple URLs must be space-separated, not comma-separated. The commas make each URL invalid.
Correct: single absolute URL
<ahref="https://example.com"ping="https://example.com/track">Visit Example</a>
Correct: multiple space-separated absolute URLs
<ahref="https://example.com"ping="https://example.com/track https://analytics.example.com/log">Visit Example</a>
Each URL is a fully qualified https URL, and they are separated by a single space. Both will receive a POST request when the link is clicked (in browsers that support the ping attribute).
The poster attribute specifies an image to display as a placeholder while the video is loading or before the user starts playback. Like all HTML attributes that accept URLs — such as src, href, and action — the value must conform to valid URI syntax as defined by RFC 3986. In this standard, a literal space character is not a legal character in any part of a URL. When the validator encounters a space in the poster attribute's value, it flags it as an illegal character in the path segment.
While most modern browsers are forgiving and will attempt to resolve URLs containing raw spaces by internally encoding them, relying on this behavior is problematic for several reasons:
- Standards compliance: The HTML specification requires valid URLs. Raw spaces violate this requirement.
- Interoperability: Not all user agents, HTTP clients, or content delivery systems handle unencoded spaces the same way. Some may truncate the URL at the first space or fail to resolve the resource entirely.
- Portability: If your HTML is consumed by tools, scrapers, or APIs that strictly parse URLs, unencoded spaces can cause silent failures.
- Consistency: Keeping URLs properly encoded prevents subtle bugs when paths are constructed dynamically in server-side or client-side code.
The fix is straightforward. You have two options:
- Percent-encode the spaces: Replace every space in the URL with
%20. This preserves the original file and folder names on the server while producing a valid URL in your HTML. - Eliminate spaces from file and folder names: Use hyphens (
-), underscores (_), or camelCase instead of spaces. This is generally considered best practice for web assets, as it avoids encoding issues across the board.
Note that this rule applies to the entire URL path, not just the filename. If any directory in the path contains a space, it must also be encoded or renamed. The same principle applies to other special characters that are reserved or disallowed in URLs, such as {, }, |, ^, and [.
Examples
Incorrect — space in the path
The folder name video images contains a space, which is illegal in a URL path segment.
<videocontrolsposter="/img/video images/snapshot.png">
<sourcesrc="/videos/sample.mp4"type="video/mp4">
</video>
Incorrect — space in the filename
The filename my poster.jpg also triggers the same error.
<videocontrolsposter="/img/my poster.jpg">
<sourcesrc="/videos/sample.mp4"type="video/mp4">
</video>
Fixed — percent-encoding the spaces
Each space is replaced with %20, producing a valid URL.
<videocontrolsposter="/img/video%20images/snapshot.png">
<sourcesrc="/videos/sample.mp4"type="video/mp4">
</video>
Fixed — removing spaces from the path
Renaming the folder to use a hyphen eliminates the need for encoding entirely.
<videocontrolsposter="/img/video-images/snapshot.png">
<sourcesrc="/videos/sample.mp4"type="video/mp4">
</video>
Fixed — removing spaces from the filename
<videocontrolsposter="/img/my-poster.jpg">
<sourcesrc="/videos/sample.mp4"type="video/mp4">
</video>
As a general best practice, avoid spaces in all file and folder names used on the web. Use hyphens or underscores instead. If you're working with files you can't rename — such as assets from a CMS or third-party system — always percent-encode spaces as %20 in your HTML. This applies not only to poster but to every attribute that takes a URL value, including src, href, action, data, and formaction.
The rel attribute defines the relationship between the current document and a linked resource. The HTML specification maintains a set of recognized keyword values for this attribute, and the allowed keywords vary depending on which element the attribute appears on. For example, stylesheet is valid on <link> but not on <a>, while nofollow is valid on <a> and <form> but not on <link>.
When the validator encounters a rel value that isn't a recognized keyword, it checks whether the value is a valid absolute URL. This is because the HTML specification allows custom link types to be defined using absolute URLs as identifiers (similar to how XML namespaces work). If the value is neither a recognized keyword nor a valid absolute URL, the validator raises this error.
Common causes of this error include:
- Typos in standard keywords — for example,
rel="styelsheet"orrel="no-follow"instead of the correctrel="stylesheet"orrel="nofollow". - Using non-standard or invented values — such as
rel="custom"orrel="external", which aren't part of the HTML specification's recognized set. - Using relative URLs as custom link types — for example,
rel="my-custom-type"instead of a full URL likerel="https://example.com/my-custom-type".
This matters because browsers and other user agents rely on recognized rel values to determine how to handle linked resources. An unrecognized value will simply be ignored, which could mean your stylesheet doesn't load, your prefetch hint doesn't work, or search engines don't respect your intended link relationship. Using correct values ensures predictable behavior across all browsers and tools.
Examples
Incorrect: Misspelled keyword
<linkrel="styleshet"href="main.css">
The validator reports that styleshet is not a recognized keyword and is not an absolute URL.
Correct: Fixed spelling
<linkrel="stylesheet"href="main.css">
Incorrect: Non-standard keyword on an anchor
<ahref="https://example.com"rel="external">Visit Example</a>
The value external is not a standard rel keyword in the HTML specification, so the validator flags it.
Correct: Using a recognized keyword
<ahref="https://example.com"rel="noopener">Visit Example</a>
Incorrect: Relative URL as a custom link type
<linkrel="my-custom-rel"href="data.json">
Correct: Absolute URL as a custom link type
If you genuinely need a custom relationship type, provide a full absolute URL:
<linkrel="https://example.com/rels/my-custom-rel"href="data.json">
Correct: Common valid rel values
Here are some frequently used standard rel keywords with their appropriate elements:
<!-- Linking a stylesheet -->
<linkrel="stylesheet"href="styles.css">
<!-- Linking a favicon -->
<linkrel="icon"href="favicon.ico">
<!-- Preloading a resource -->
<linkrel="preload"href="font.woff2"as="font"type="font/woff2"crossorigin>
<!-- Telling search engines not to follow a link -->
<ahref="https://example.com"rel="nofollow">Sponsored link</a>
<!-- Opening a link safely in a new tab -->
<ahref="https://example.com"target="_blank"rel="noopener noreferrer">External site</a>
Multiple rel values
You can specify multiple space-separated rel values. Each one must individually be either a recognized keyword or a valid absolute URL:
<!-- Correct: both values are recognized keywords -->
<ahref="https://example.com"target="_blank"rel="noopener noreferrer">External</a>
<!-- Incorrect: "popup" is not a recognized keyword or absolute URL -->
<ahref="https://example.com"target="_blank"rel="noopener popup">External</a>
To resolve this error, consult the MDN rel attribute reference for the full list of recognized keywords and which elements support them. If your value isn't on the list, either replace it with the correct standard keyword or use a complete absolute URL to define your custom link type.
In HTML, boolean attributes like required work differently from what many developers expect. Their presence on an element means the value is true, and their absence means the value is false. According to the WHATWG HTML specification, a boolean attribute's value must either be the empty string or an ASCII case-insensitive match for the attribute's canonical name. For the required attribute, this means the only valid values are "" (empty string) and "required".
A common mistake is writing required="true" or required="false". The value "true" is not a valid boolean attribute value in HTML and will trigger this validation error. Even more confusingly, writing required="false" does not make the input optional — since the attribute is still present, the browser still treats the field as required. This can lead to subtle bugs where a form field appears to be optional in your code but is actually enforced as required by the browser.
This matters for several reasons:
- Standards compliance: Invalid attribute values violate the HTML specification and will cause W3C validation errors.
- Code clarity: Using non-standard values like
"true"or"false"misleads other developers about how the attribute works. - Unexpected behavior:
required="false"still makes the field required, which can cause confusing form behavior.
To make a field optional, simply remove the required attribute entirely rather than setting it to "false".
Examples
Incorrect: Invalid values for required
These will all trigger the "Bad value for attribute required" validation error:
<inputtype="text"required="true">
<inputtype="email"required="false">
<inputtype="text"required="yes">
<inputtype="text"required="1">
Correct: Valid uses of the required attribute
All three of these forms are valid and make the input required:
<!-- Preferred: no value (most concise) -->
<inputtype="text"required>
<!-- Also valid: empty string -->
<inputtype="text"required="">
<!-- Also valid: canonical name as value -->
<inputtype="text"required="required">
Correct: Making a field optional
To make a field optional, remove the attribute entirely:
<inputtype="text">
Full form example
<formaction="/submit"method="post">
<labelfor="name">Name (required):</label>
<inputtype="text"id="name"name="name"required>
<labelfor="notes">Notes (optional):</label>
<inputtype="text"id="notes"name="notes">
<buttontype="submit">Submit</button>
</form>
This same rule applies to other boolean attributes in HTML, such as disabled, checked, readonly, multiple, and autofocus. They all follow the same pattern: use the attribute with no value, with an empty string, or with the attribute's own name as the value. Never assign "true" or "false" to them.
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
sandboxattribute provides no meaningful security benefit, and you may want to use other security mechanisms likeContent-Security-Policyheaders 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 sizes attribute works together with srcset to help the browser choose the most appropriate image source for the current layout. It accepts a comma-separated list of entries, where each entry is an optional media condition followed by a CSS length value (called a "source size value"). The browser evaluates these entries to determine the intended display width of the image before it downloads it. Valid length values include viewport-relative units like 100vw, absolute units like 472px, or calc() expressions like calc(100vw - 2rem).
The value auto was not part of the original HTML specification for the sizes attribute. However, the sizes="auto" value has been added to the HTML living standard specifically for use with lazy-loaded images (loading="lazy"). When both loading="lazy" and sizes="auto" are present, the browser can defer size calculation until layout time, since the image won't be fetched immediately anyway. Some validators may not yet recognize this newer addition, or the error may appear because auto is being used without loading="lazy", or combined incorrectly with other size entries like sizes="auto, 100vw".
This validation error matters for several reasons. First, if the browser doesn't understand the sizes value, it may fall back to a default of 100vw, which could cause it to download a larger image than necessary, hurting performance. Second, malformed attribute values can lead to unpredictable behavior across different browsers. Third, standards compliance ensures your markup works reliably now and in the future.
How to Fix
You have a few options depending on your situation:
Replace
autowith a valid CSS length. If you know the intended display size of the image, specify it directly. This is the most broadly compatible approach.Use
sizes="auto"only withloading="lazy". If you want the browser to automatically determine the size, ensure you also includeloading="lazy"and awidthattribute on the image. Note that some validators may still flag this until they update their rules.Remove
autofrom a comma-separated list. If you have something likesizes="auto, (max-width: 600px) 100vw, 50vw", remove theautoentry entirely.
Examples
Incorrect: Using auto without lazy loading
This triggers the validation error because auto is not a valid CSS length in the traditional sizes syntax.
<img
src="image.jpg"
srcset="image-small.jpg 300w, image-medium.jpg 600w, image-large.jpg 1000w"
sizes="auto, 100vw"
alt="A scenic mountain landscape"
>
Fixed: Using a valid CSS length value
Replace auto with a concrete size or a set of media-conditioned sizes.
<img
src="image.jpg"
srcset="image-small.jpg 300w, image-medium.jpg 600w, image-large.jpg 1000w"
sizes="(max-width: 472px) 100vw, 472px"
alt="A scenic mountain landscape"
>
In this example, when the viewport is 472 pixels wide or smaller, the image takes up the full viewport width (100vw). For wider viewports, the browser knows the image will display at 472px wide and selects the best source from srcset accordingly.
Fixed: Using auto with lazy loading
If you want the browser to determine the display size automatically, pair sizes="auto" with loading="lazy" and explicit dimensions.
<img
src="image.jpg"
srcset="image-small.jpg 300w, image-medium.jpg 600w, image-large.jpg 1000w"
sizes="auto"
width="600"
height="400"
loading="lazy"
alt="A scenic mountain landscape"
>
The width and height attributes help the browser reserve the correct space in the layout, and loading="lazy" allows the browser to defer both loading and size calculation until the image is near the viewport.
Fixed: Using calc() for dynamic sizing
If your image sits inside a container with padding, you can use calc() for a precise size hint.
<img
src="image.jpg"
srcset="image-small.jpg 300w, image-medium.jpg 600w, image-large.jpg 1000w"
sizes="calc(100vw - 2rem)"
alt="A scenic mountain landscape"
>
The sizes attribute tells the browser how wide an image will be displayed under different viewport conditions, allowing it to choose the best candidate from srcset before the page layout is calculated. Its value is a comma-separated list where each entry (except optionally the last) pairs a media condition with a CSS length. The final entry can be a bare length that serves as the default fallback. The full syntax looks like this:
(media-condition) length, (media-condition) length, fallback-length
Media conditions use the same grammar as CSS media queries. They must be enclosed in parentheses and can use logical operators like and, or, and not to combine sub-expressions. The slot size (length) that follows each condition must use a valid CSS length unit such as px, em, rem, vw, vh, vmin, vmax, or ch. Percentages are not valid in sizes slot lengths, and unitless numbers (other than 0) are also invalid.
This error matters for several reasons. First, if the browser cannot parse the sizes value, it falls back to a default of 100vw, which may cause it to download an unnecessarily large image — hurting performance and wasting bandwidth. Second, an invalid sizes attribute signals a misunderstanding of responsive image markup that could lead to layout or rendering issues. Third, standards-compliant HTML ensures consistent behavior across all browsers.
Common Parse Errors
Here are the most frequent mistakes that trigger this validation error:
- Missing parentheses around the media condition. Writing
min-width: 600pxinstead of(min-width: 600px). - Missing slot size after a condition. Each media condition must be followed by a length value, e.g.,
(min-width: 600px) 50vw. Writing(min-width: 600px), 100vwwithout a slot size after the condition is invalid — the browser interprets(min-width: 600px)alone as the entry. - Using percentages as slot sizes. Values like
50%are not allowed; use viewport-relative units like50vwinstead. - Unitless numbers. A slot size of
300is invalid; it must be300pxor another valid length. - Trailing or extra commas. A trailing comma after the last entry or double commas between entries will cause a parse error.
- Mixing up range syntax. Modern media query range syntax like
(width >= 600px)is valid, but hybrid forms like(min-width <= 600px)are not. Use either the traditional(min-width: 600px)or the range form(width >= 600px). - Invalid tokens or typos. Misspelled feature names (e.g.,
min-widht) or unsupported tokens will break parsing.
How to Fix
- Wrap every media condition in parentheses. Even simple conditions need them:
(min-width: 600px). - Follow each condition with a valid length. For example,
(min-width: 600px) 50vw. - End with a fallback length. The last item should be a bare length with no media condition, like
100vw. - Use valid CSS length units for slot sizes —
px,em,rem,vw,vh, etc. Never use%. - Ensure your
srcsetuseswdescriptors that match the intrinsic pixel widths of the image files, sincesizesonly works with width descriptors.
Examples
Incorrect: missing parentheses and missing slot size
The media condition lacks parentheses, and there is no slot size telling the browser how wide the image will be when the condition matches.
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="min-width: 600px, 100vw"
alt="A mountain landscape">
Correct: parentheses and slot size added
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="(min-width: 600px) 50vw, 100vw"
alt="A mountain landscape">
Incorrect: percentage used as slot size
<img
src="banner-1200.jpg"
srcset="banner-600.jpg 600w, banner-1200.jpg 1200w"
sizes="(min-width: 768px) 50%, 100%"
alt="Promotional banner">
Correct: viewport units instead of percentages
<img
src="banner-1200.jpg"
srcset="banner-600.jpg 600w, banner-1200.jpg 1200w"
sizes="(min-width: 768px) 50vw, 100vw"
alt="Promotional banner">
Incorrect: trailing comma
<img
src="icon-256.png"
srcset="icon-128.png 128w, icon-256.png 256w"
sizes="(min-width: 1024px) 128px, 64px,"
alt="App icon">
Correct: trailing comma removed
<img
src="icon-256.png"
srcset="icon-128.png 128w, icon-256.png 256w"
sizes="(min-width: 1024px) 128px, 64px"
alt="App icon">
Incorrect: invalid range syntax mixing traditional and modern forms
<img
src="hero-1600.jpg"
srcset="hero-800.jpg 800w, hero-1600.jpg 1600w"
sizes="(min-width <= 600px) 100vw, 50vw"
alt="Hero image">
Correct: using proper range syntax
<img
src="hero-1600.jpg"
srcset="hero-800.jpg 800w, hero-1600.jpg 1600w"
sizes="(width <= 600px) 100vw, 50vw"
alt="Hero image">
Multiple conditions with a fallback
This example shows a fully valid sizes attribute with several breakpoints, combining traditional and modern range syntax across entries.
<img
src="article-1200.jpg"
srcset="article-400.jpg 400w, article-800.jpg 800w, article-1200.jpg 1200w"
sizes="(min-width: 1200px) 33vw, (min-width: 768px) 50vw, 100vw"
alt="Article thumbnail">
Using calc() in slot sizes
You can use calc() for more precise slot sizes. This is fully valid in the sizes attribute.
<img
src="card-800.jpg"
srcset="card-400.jpg 400w, card-800.jpg 800w"
sizes="(min-width: 960px) calc(33.33vw - 2rem), calc(100vw - 2rem)"
alt="Product card">
The sizes attribute works alongside srcset to tell the browser how wide an image will be displayed at various viewport sizes, so the browser can choose the most appropriate image source before the page layout is computed. The attribute value is a comma-separated list where each entry consists of an optional media condition followed by a source size value (a CSS length). The final entry in the list acts as the default size and should not include a media condition.
When the browser parses the sizes attribute and encounters an empty entry — meaning there is nothing meaningful between two commas or after a trailing comma — it cannot resolve that entry to a valid source size. The HTML living standard's parsing algorithm treats this as an error. While most browsers will silently ignore the empty entry and still render the image, the malformed attribute can lead to unexpected behavior and indicates a code quality issue that should be addressed.
Common causes of this error include:
- A trailing comma at the end of the
sizesvalue (e.g.,"100vw, 50vw, "). - Double commas in the middle of the list (e.g.,
"100vw, , 50vw"). - Dynamically generated values where a template or CMS outputs a comma even when a size entry is conditionally omitted.
Fixing the issue is straightforward: ensure every comma in the list separates two valid source size entries, and that the list neither begins nor ends with a comma.
Examples
Trailing comma (invalid)
The most common cause — a comma after the last entry creates an empty source size:
<img
src="photo.jpg"
alt="A mountain landscape"
sizes="(min-width: 1200px) 800px, (min-width: 600px) 400px, 100vw, "
srcset="photo-800.jpg 800w, photo-400.jpg 400w, photo-200.jpg 200w">
Trailing comma fixed
Remove the trailing comma so the list ends with a valid entry:
<img
src="photo.jpg"
alt="A mountain landscape"
sizes="(min-width: 1200px) 800px, (min-width: 600px) 400px, 100vw"
srcset="photo-800.jpg 800w, photo-400.jpg 400w, photo-200.jpg 200w">
Double comma in the middle (invalid)
A double comma creates an empty entry between two valid sizes:
<img
src="banner.jpg"
alt="Promotional banner"
sizes="(min-width: 1024px) 960px, , 100vw"
srcset="banner-960.jpg 960w, banner-480.jpg 480w">
Double comma fixed
Remove the extra comma so each entry is separated by exactly one comma:
<img
src="banner.jpg"
alt="Promotional banner"
sizes="(min-width: 1024px) 960px, 100vw"
srcset="banner-960.jpg 960w, banner-480.jpg 480w">
Dynamically generated sizes with an empty entry (invalid)
Templates or CMS platforms sometimes output commas for entries that are conditionally empty:
<img
src="hero.jpg"
alt="Hero image"
sizes="(min-width: 1400px) 1200px, , (min-width: 768px) 700px, 100vw"
srcset="hero-1200.jpg 1200w, hero-700.jpg 700w, hero-350.jpg 350w">
Dynamically generated sizes fixed
Ensure the template logic only outputs a comma when a valid entry follows:
<img
src="hero.jpg"
alt="Hero image"
sizes="(min-width: 1400px) 1200px, (min-width: 768px) 700px, 100vw"
srcset="hero-1200.jpg 1200w, hero-700.jpg 700w, hero-350.jpg 350w">
If your sizes attribute is built dynamically, consider filtering out empty values before joining them with commas, or trimming trailing commas from the final output. A well-formed sizes attribute should always consist of one or more valid entries separated by single commas, with the last entry serving as the default source size (typically something like 100vw).
The sizes and srcset attributes work together to enable responsive images, but they serve distinct roles and use different syntax. The srcset attribute lists available image files along with their intrinsic widths using the w descriptor (e.g., 800w means the image file is 800 pixels wide). The sizes attribute, on the other hand, tells the browser how wide the image will actually be rendered in the layout, using standard CSS length units. The browser combines this information — knowing which files are available and how large the image will appear — to choose the most efficient file to download.
A common mistake is mixing up these two syntaxes, typically by copying a width descriptor like 860w from srcset and placing it into sizes. Since w is not a CSS unit, the validator rejects it. This matters because an invalid sizes value prevents the browser from correctly calculating which image source to use, potentially causing it to download an unnecessarily large image (wasting bandwidth) or a too-small image (resulting in poor quality).
How the sizes attribute works
The sizes attribute accepts a comma-separated list of media conditions paired with CSS lengths, plus an optional default length at the end. Each entry follows the pattern (media-condition) length. The browser evaluates the conditions in order and uses the length from the first matching condition. If none match, it uses the final default value.
Valid CSS length units include px, em, rem, vw, vh, ch, cm, mm, in, pt, pc, and CSS calc() expressions. You can combine units with calc() for more precise sizing — for example, calc(100vw - 2rem).
Examples
❌ Incorrect: using w in sizes
This triggers the validation error because 860w is a srcset width descriptor, not a CSS length:
<img
alt="A landscape photo"
sizes="860w"
srcset="photo-small.jpg 430w, photo-large.jpg 860w"
src="photo-large.jpg">
✅ Correct: using px in sizes
Replace the w value with a CSS length that reflects the image's actual display size:
<img
alt="A landscape photo"
sizes="860px"
srcset="photo-small.jpg 430w, photo-large.jpg 860w"
src="photo-large.jpg">
✅ Correct: responsive sizes with media conditions
Use media conditions to specify different display sizes at different viewport widths:
<img
alt="A landscape photo"
sizes="(min-width: 1024px) 860px, (min-width: 568px) 430px, 100vw"
srcset="photo-small.jpg 430w, photo-large.jpg 860w"
src="photo-large.jpg">
This tells the browser:
- On viewports 1024px and wider, the image displays at 860px wide.
- On viewports 568px and wider, the image displays at 430px wide.
- On smaller viewports, the image fills the full viewport width (
100vw).
✅ Correct: using calc() in sizes
When the image width depends on padding or margins, calc() provides precise sizing:
<img
alt="A landscape photo"
sizes="(min-width: 768px) calc(50vw - 2rem), calc(100vw - 1rem)"
srcset="photo-small.jpg 400w, photo-medium.jpg 800w, photo-large.jpg 1200w"
src="photo-medium.jpg">
Key takeaways
- The
wdescriptor belongs only insrcset, where it describes the intrinsic width of each image file. - The
sizesattribute uses CSS length units (px,vw,em,calc(), etc.) to describe how wide the image will appear on screen. - If your image always displays at a fixed width, a simple value like
sizes="300px"is sufficient. - If your image width varies by viewport, use media conditions with appropriate CSS lengths to give the browser the information it needs to select the best source.
How the sizes Attribute Works
The sizes attribute works alongside srcset to enable responsive images. When you provide srcset with width descriptors (e.g., 400w, 800w), the browser needs to know how wide the image slot will actually be on screen so it can pick the best candidate. That's what sizes provides — a comma-separated list of size descriptors that tell the browser the rendered width of the image under various viewport conditions.
Each entry in the list follows this pattern:
- Optional media condition followed by a CSS length:
(max-width: 600px) 100vw - A final fallback length with no media condition:
33vw
The browser evaluates media conditions from left to right and uses the length from the first matching condition. If no condition matches, the fallback is used.
Why This Error Occurs
The validator checks that every length token in sizes uses one of the recognized CSS absolute or relative length units: em, ex, ch, rem, cap, ic, vw, vh, vmin, vmax, cm, mm, q, in, pc, pt, px, and their small/large/dynamic viewport variants (svw, lvw, dvw, svh, lvh, dvh, svi, lvi, dvi, svb, lvb, dvb, svmin, lvmin, dvmin, svmax, lvmax, dvmax).
Percentages (%) are not allowed. This is a common point of confusion — while % is valid in most CSS contexts, the sizes attribute explicitly forbids it because a percentage would be ambiguous (percentage of what?). The vw unit is typically the correct replacement when you want to express a fraction of the viewport width.
Here are the most common mistakes that trigger this error:
- Missing units:
sizes="100"— a bare number has no meaning without a unit. - Using percentages:
sizes="50%"— use50vwinstead. - Typos in unit names:
100pxx,100vws,50 vw(with a space between number and unit). - Multiple lengths in a single entry:
sizes="(min-width: 800px) 50vw 400px"— each entry must contain exactly one length. - Using
calc()incorrectly: Whilecalc()is valid insizes, the expressions inside it must also use valid units.
Why It Matters
When the sizes value is malformed, browsers fall back to a default of 100vw, which means every image is treated as if it spans the full viewport width. This defeats the purpose of responsive images — the browser may download unnecessarily large files on small screens, wasting bandwidth and slowing page loads. Valid sizes values are essential for proper image optimization.
Additionally, invalid HTML can cause unpredictable behavior across different browsers and versions. Standards-compliant markup ensures consistent rendering and forward compatibility.
How to Fix It
- Find the position indicated in the error message (the "at Z" part) — this tells you exactly where in the
sizesstring the invalid token was found. - Check for bare numbers and add the appropriate unit (
px,vw,em, etc.). - Replace
%withvwif you intended a percentage of the viewport width. - Fix any typos in unit names.
- Ensure each comma-separated entry has exactly one length, optionally preceded by a media condition in parentheses.
- Verify there's no space between the number and its unit —
100vwis correct,100 vwis not.
Examples
❌ Bare number without a unit
<img
src="photo-400.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="(max-width: 600px) 100, 400"
alt="A landscape photo">
✅ Fixed: add vw and px units
<img
src="photo-400.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="(max-width: 600px) 100vw, 400px"
alt="A landscape photo">
❌ Using invalid percentage
<img
src="banner-800.jpg"
srcset="banner-800.jpg 800w, banner-1600.jpg 1600w"
sizes="(max-width: 700px) 100%, 80%"
alt="Promotional banner">
✅ Fixed: replace % with vw
<img
src="banner-800.jpg"
srcset="banner-800.jpg 800w, banner-1600.jpg 1600w"
sizes="(max-width: 700px) 100vw, 80vw"
alt="Promotional banner">
❌ Multiple lengths in one entry
<img
src="hero-640.jpg"
srcset="hero-640.jpg 640w, hero-1280.jpg 1280w"
sizes="(min-width: 800px) 50vw 400px, 100vw"
alt="Hero image">
✅ Fixed: one length per entry, separated by commas
<img
src="hero-640.jpg"
srcset="hero-640.jpg 640w, hero-1280.jpg 1280w"
sizes="(min-width: 800px) 50vw, 100vw"
alt="Hero image">
❌ Typo in unit name
<img
src="thumb-320.jpg"
srcset="thumb-320.jpg 320w, thumb-640.jpg 640w"
sizes="320pxx"
alt="Thumbnail">
✅ Fixed: correct the unit
<img
src="thumb-320.jpg"
srcset="thumb-320.jpg 320w, thumb-640.jpg 640w"
sizes="320px"
alt="Thumbnail">
✅ Multiple media conditions with a fallback
<img
src="photo-640.jpg"
srcset="photo-640.jpg 640w, photo-960.jpg 960w, photo-1280.jpg 1280w"
sizes="(min-width: 1200px) 800px, (min-width: 800px) 60vw, 90vw"
alt="Landscape photo">
✅ Using calc() with valid units
<img
src="article-img-400.jpg"
srcset="article-img-400.jpg 400w, article-img-800.jpg 800w"
sizes="(min-width: 960px) calc(50vw - 2rem), 100vw"
alt="Article illustration">
The calc() function is valid inside sizes and is useful when the image width depends on a combination of viewport size and fixed spacing like padding or margins. Just make sure every value inside calc() also uses valid units.
The sizes attribute tells the browser how wide an image will be displayed at different viewport sizes, so it can choose the best candidate from srcset. It only applies when srcset uses width descriptors (e.g., 480w, 800w). The value is a comma-separated list where:
- Each entry (except the last) is a media condition in parentheses followed by a CSS length — for example,
(min-width: 800px) 50vw. - The final entry is a fallback length with no media condition — for example,
100vw.
The browser evaluates the media conditions from left to right and uses the length from the first one that matches. If none match, it uses the fallback.
The media condition syntax mirrors CSS media queries but without the @media keyword. You can use logical operators like and, or, and not, and features like min-width, max-width, orientation, etc.
Why This Error Occurs
The validator parses the sizes value according to the HTML living standard and CSS Media Queries specification. A parse error is triggered when the value doesn't conform to the expected grammar. Common causes include:
- Missing parentheses around the media condition:
min-width: 600px 50vwinstead of(min-width: 600px) 50vw. - Missing length after a media condition:
(min-width: 800px)with no length following it. - Invalid or missing units on the length:
50instead of50vw,50px, or50rem. - Trailing commas or extra separators:
(min-width: 600px) 50vw, , 100vw. - Invalid media feature syntax: typos like
(minwidth: 600px)or using unsupported tokens. - Using
sizeswith pixel-density descriptors: whensrcsetuses1x,2xinstead of width descriptors, thesizesattribute is meaningless and can confuse validators.
Why It Matters
- Browser image selection: Browsers rely on a correctly parsed
sizesvalue to pick the optimal image fromsrcset. A malformed value causes the browser to fall back to a default size (typically100vw), which can result in downloading unnecessarily large images on small screens or blurry images on large screens. - Standards compliance: Invalid
sizesvalues violate the HTML specification and may behave unpredictably across different browsers. - Performance: Correct
sizesvalues are essential for responsive image optimization. Without them, you lose the bandwidth savings thatsrcsetwith width descriptors is designed to provide.
How to Fix It
- Wrap every media condition in parentheses:
(min-width: 600px), notmin-width: 600px. - Always include a valid CSS length after each condition:
(min-width: 600px) 50vw, not just(min-width: 600px). - End with a fallback length that has no condition:
100vw. - Use valid CSS length units:
vw,px,em,rem, orcalc()expressions. - Remove trailing or duplicate commas.
- Omit
sizesentirely if yoursrcsetuses pixel-density descriptors (1x,2x).
Examples
Incorrect: missing parentheses around the media condition
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="min-width: 600px 50vw, 100vw"
alt="A landscape photo">
Correct: parentheses added
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="(min-width: 600px) 50vw, 100vw"
alt="A landscape photo">
Incorrect: missing length after the media condition
<img
src="banner-800.jpg"
srcset="banner-400.jpg 400w, banner-800.jpg 800w"
sizes="(min-width: 700px), 100vw"
alt="A promotional banner">
The first entry (min-width: 700px) has no length value — the comma makes it look like a separate entry, but it's incomplete.
Correct: length added after the condition
<img
src="banner-800.jpg"
srcset="banner-400.jpg 400w, banner-800.jpg 800w"
sizes="(min-width: 700px) 60vw, 100vw"
alt="A promotional banner">
Incorrect: using sizes with pixel-density descriptors
<img
src="avatar.jpg"
srcset="avatar@1x.jpg 1x, avatar@2x.jpg 2x"
sizes="(min-width: 600px) 80px, 40px"
alt="User avatar">
The sizes attribute is only meaningful with width descriptors (w). When srcset uses density descriptors (1x, 2x), omit sizes.
Correct: sizes removed for density descriptors
<img
src="avatar.jpg"
srcset="avatar@1x.jpg 1x, avatar@2x.jpg 2x"
alt="User avatar">
Incorrect: trailing comma
<img
src="hero-800.jpg"
srcset="hero-400.jpg 400w, hero-800.jpg 800w"
sizes="(min-width: 800px) 800px, 100vw,"
alt="Hero image">
Correct: trailing comma removed
<img
src="hero-800.jpg"
srcset="hero-400.jpg 400w, hero-800.jpg 800w"
sizes="(min-width: 800px) 800px, 100vw"
alt="Hero image">
Using sizes on a <source> inside <picture>
<picture>
<source
type="image/webp"
srcset="hero-480.webp 480w, hero-800.webp 800w, hero-1200.webp 1200w"
sizes="(min-width: 1200px) 1200px, (min-width: 800px) 800px, 100vw">
<img
src="hero-800.jpg"
srcset="hero-480.jpg 480w, hero-800.jpg 800w, hero-1200.jpg 1200w"
sizes="(min-width: 1200px) 1200px, (min-width: 800px) 800px, 100vw"
alt="Hero image">
</picture>
Using compound media conditions
You can combine conditions with and:
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
sizes="(min-width: 900px) and (orientation: landscape) 50vw, 100vw"
alt="A sample photo">
Full valid document
<!doctype html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>Responsive Image Example</title>
</head>
<body>
<img
src="pic-800.jpg"
srcset="pic-400.jpg 400w, pic-800.jpg 800w"
sizes="(min-width: 600px) 50vw, 100vw"
alt="A picture demonstrating valid sizes usage">
</body>
</html>
The sizes attribute tells the browser how wide an image will be displayed at various viewport sizes, so it can choose the best candidate from srcset before the CSS has loaded. Each entry in the sizes list is either a media condition paired with a CSS length, or a bare fallback length at the end. The HTML specification requires these lengths to be valid CSS <length> values, which means a number followed by a recognized unit. The full list of accepted units includes px, em, ex, ch, rem, vw, vh, vmin, vmax, cm, mm, in, pt, pc, and newer viewport units like svw, lvw, dvw, svh, lvh, dvh, and others.
The calc() function is also permitted, which lets you combine units — for example, calc(100vw - 2rem).
Why this matters
The sizes attribute is parsed before layout occurs, meaning the browser relies on it being syntactically correct to make smart download decisions. An invalid value forces the browser to fall back to a default size (typically 100vw), which can lead to downloading images that are far too large or too small. Beyond performance, an invalid sizes value violates the HTML specification, and some browsers may handle the malformed input inconsistently, leading to unpredictable behavior across devices.
Common mistakes
- Missing unit entirely: Writing
800instead of800px. Bare numbers without units are not valid CSS lengths (the only exception in CSS is0, but even0should have a unit insizesfor clarity). - Using percentages: Writing
50%instead of50vw. Percentages are not allowed insizesbecause the browser hasn't performed layout yet and doesn't know what the percentage would be relative to. Use viewport units likevwinstead. - Typos or extra characters: A stray character, misplaced comma, or missing whitespace can shift the parser's position and cause it to find unexpected tokens where it expects a unit.
- Using
autoon<source>: Theautokeyword forsizesis only valid on<img>elements (and requires theloading="lazy"attribute). Using it on<source>will trigger a validation error.
How to fix it
- Find the position indicated in the error message ("at Z") to locate the problematic value.
- If the value is a bare number, append the correct unit (e.g., change
600to600px). - If the value uses
%, replace it with a viewport-relative unit likevw. - Ensure commas separate each media-condition/length pair, and that the list ends with a fallback length.
Examples
Adding a missing unit
The value 800 in the sizes attribute is not a valid CSS length because it has no unit.
<!-- ❌ Invalid: "800" is missing a unit -->
<img
src="hero-800.jpg"
srcset="hero-800.jpg 800w, hero-1200.jpg 1200w"
sizes="(min-width: 900px) 800, 100vw"
alt="Hero image">
<!-- ✅ Valid: "800px" has a proper unit -->
<img
src="hero-800.jpg"
srcset="hero-800.jpg 800w, hero-1200.jpg 1200w"
sizes="(min-width: 900px) 800px, 100vw"
alt="Hero image">
Replacing a percentage with a viewport unit
Percentages are not permitted in sizes. Use vw to express a width relative to the viewport.
<!-- ❌ Invalid: "50%" is not an allowed unit in sizes -->
<img
src="card-600.jpg"
srcset="card-600.jpg 600w, card-900.jpg 900w"
sizes="50%"
alt="Product card">
<!-- ✅ Valid: "50vw" means 50% of the viewport width -->
<img
src="card-600.jpg"
srcset="card-600.jpg 600w, card-900.jpg 900w"
sizes="50vw"
alt="Product card">
Fixing the issue on a <source> element
The same rules apply to <source> elements inside <picture>. Every length value needs a valid unit.
<!-- ❌ Invalid: "600" on <source> is missing a unit -->
<picture>
<source
type="image/webp"
srcset="hero-600.webp 600w, hero-1200.webp 1200w"
sizes="(min-width: 768px) 600, 100vw">
<img
src="hero-600.jpg"
srcset="hero-600.jpg 600w, hero-1200.jpg 1200w"
sizes="(min-width: 768px) 600px, 100vw"
alt="Hero image">
</picture>
<!-- ✅ Valid: both <source> and <img> use proper units -->
<picture>
<source
type="image/webp"
srcset="hero-600.webp 600w, hero-1200.webp 1200w"
sizes="(min-width: 768px) 600px, 100vw">
<img
src="hero-600.jpg"
srcset="hero-600.jpg 600w, hero-1200.jpg 1200w"
sizes="(min-width: 768px) 600px, 100vw"
alt="Hero image">
</picture>
Using calc() for mixed-unit sizes
You can use calc() to combine different units, which is especially useful when accounting for padding or margins.
<img
src="article-800.jpg"
srcset="article-800.jpg 800w, article-1200.jpg 1200w"
sizes="(min-width: 1024px) calc(100vw - 4rem), 100vw"
alt="Article image">
Multiple slots with different conditions
A well-formed sizes attribute with several media conditions, ending with a unitless-condition fallback length:
<picture>
<source
type="image/avif"
srcset="banner-480.avif 480w, banner-960.avif 960w, banner-1440.avif 1440w"
sizes="(min-width: 1200px) 960px, (min-width: 600px) 80vw, 100vw">
<img
src="banner-480.jpg"
srcset="banner-480.jpg 480w, banner-960.jpg 960w, banner-1440.jpg 1440w"
sizes="(min-width: 1200px) 960px, (min-width: 600px) 80vw, 100vw"
alt="Responsive banner">
</picture>
The general pattern is: sizes="(condition) length, (condition) length, fallback-length". Every length in that list — including the fallback — must be a valid CSS length with a unit or a calc() expression. If the validator error points to a specific character position, inspect that exact spot for a missing unit, a stray %, or malformed whitespace and replace it with one of the recognized units.
A URL follows a specific structure defined by RFC 3986. The general format is:
scheme://host/path?query#fragment
The # character serves as the delimiter that introduces the fragment portion of the URL. It may only appear once in this role. Once the parser encounters the first #, everything after it is treated as the fragment identifier. A second # within that fragment is an illegal character because the fragment production in the URL specification does not permit unescaped # characters.
This validation error commonly arises from:
- Duplicate
#characters — e.g., accidentally including two hash marks like/#?param=value#section. - Misplaced query strings — putting
?key=valueafter the#instead of before it. While this may work in some single-page application routers, it results in the query being part of the fragment, and adding another#after that creates an invalid URL. - Copy-paste errors — assembling URLs from multiple parts and inadvertently introducing an extra
#.
This matters for several reasons. Browsers may handle malformed URLs inconsistently — some may silently truncate or ignore part of the URL, while others may fail to load the resource entirely. The W3C validator flags this because it violates the HTML specification's requirement that the src attribute contain a valid URL. Invalid URLs can also cause issues with assistive technologies, link sharing, and automated tools that parse your HTML.
How to fix it
- Locate all
#characters in the URL and determine which one is the intended fragment delimiter. - Remove any duplicate
#characters that were added by mistake. - Move query parameters before the fragment — the
?queryportion must come before the#fragmentin a well-formed URL. - Percent-encode if needed — if a literal
#must appear as data within the fragment or query value (not as a delimiter), encode it as%23.
Examples
Incorrect: multiple # characters
The second # inside the fragment is illegal:
<iframesrc="https://example.com/#?secret=123#abc"></iframe>
Correct: query before fragment
Move the query string before the # so the URL has a proper structure:
<iframesrc="https://example.com/?secret=123#abc"></iframe>
Correct: single fragment, no query
If you only need a fragment identifier, use a single #:
<iframesrc="https://example.com/#abc"></iframe>
Correct: percent-encoding a literal # in a value
If the fragment itself must contain a # as data (not as a delimiter), percent-encode it:
<iframesrc="https://example.com/?secret=123#color=%23ff0000"></iframe>
Here, %23ff0000 represents the literal string #ff0000 within the fragment value, which is valid because the # is encoded.
Incorrect: hash-based routing with duplicate #
Some single-page app embed URLs use hash-based routing, which can lead to accidental double hashes:
<iframesrc="https://app.example.com/#/dashboard#settings"></iframe>
Correct: use a single fragment for the route
Restructure the URL to use a single # with a combined path:
<iframesrc="https://app.example.com/#/dashboard/settings"></iframe>
Or, if the application supports it, use standard path-based routing instead:
<iframesrc="https://app.example.com/dashboard/settings"></iframe>
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
srcattribute 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
<iframesrc="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
<iframesrc="https://maps.google.com/maps?q=2700%206th%20Avenue"></iframe>
✅ Fixed: spaces encoded as + in the query string
<iframesrc="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:
consturl=newURL("https://maps.google.com/maps");
url.searchParams.set("q","2700 6th Avenue");
constiframe=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.
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 theURL/URLSearchParamsAPIs - PHP:
urlencode()orhttp_build_query() - Python:
urllib.parse.urlencode()orurllib.parse.quote()
These functions will automatically encode square brackets and any other reserved characters.
Examples
❌ Invalid — unencoded square brackets in query string
<iframesrc="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
<iframesrc="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
<iframesrc="https://example.com/report?time=[2024-01-01]"></iframe>
✅ Valid — timestamp parameter properly encoded
<iframesrc="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:
<iframeid="report-frame"></iframe>
<script>
consturl=newURL("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.
Validate at scale.
Ship accessible websites, faster.
Automated HTML & accessibility validation for large sites. Check thousands of pages against WCAG guidelines and W3C standards in minutes, not days.
Pro Trial
Full Pro access. Cancel anytime.
Start Pro Trial →Join teams across 40+ countries