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.
A | (pipe) character in a link element's href attribute is not valid in a URL because it is not a legal character in a query string without being percent-encoded.
This error commonly appears when loading multiple Google Fonts in a single <link> tag using the older Google Fonts API v1 syntax, which used the pipe character to separate font families:
fonts.googleapis.com/css?family=Open+Sans|Roboto
The | character must be encoded as %7C to be valid in a URL. However, even with encoding, the Google Fonts API v1 no longer supports loading multiple families this way. The current Google Fonts API v2 (/css2) uses separate family parameters instead.
Bad example
<link rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Open+Sans|Roboto">
Good example
Use the Google Fonts API v2 with separate family parameters:
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Open+Sans&family=Roboto&display=swap">
If you are not using Google Fonts and cannot change the URL structure, percent-encode the pipe character:
<link rel="stylesheet"
href="https://example.com/css?param1=a%7Cparam2=b">
URLs used in HTML attributes must follow the URL Living Standard, which defines a specific set of characters that are allowed in each part of a URL. The query component of a URL — everything after the ? — permits most printable ASCII characters, but certain characters are still considered illegal and must be percent-encoded. When the W3C validator encounters one of these forbidden characters in the href of a <link> element, it raises this error.
Common characters that trigger this issue include:
| Character | Percent-encoded |
|---|---|
| ` | ` (pipe) |
[ (left bracket) | %5B |
] (right bracket) | %5D |
{ (left brace) | %7B |
} (right brace) | %7D |
^ (caret) | %5E |
` (backtick) | %60 |
| (space) | %20 |
Why this matters
While many modern browsers are lenient and will silently fix malformed URLs, relying on this behavior is risky. Invalid URLs can cause problems in several ways:
- Inconsistent browser behavior: Not all user agents handle illegal characters the same way, which can lead to broken stylesheets or resources failing to load.
- Interoperability issues: Proxies, CDNs, and other intermediaries may reject or mangle URLs with illegal characters.
- Standards compliance: Valid HTML requires valid URLs in attributes. An illegal character in the
hrefmakes the entire document non-conforming. - Copy-paste and sharing reliability: Malformed URLs are more likely to break when shared across systems, emails, or documentation.
How to fix it
Identify the illegal characters in your URL's query string and replace each one with its percent-encoded equivalent. If you're generating URLs programmatically, use a proper URL encoding function (e.g., encodeURIComponent() in JavaScript, urlencode() in PHP, or urllib.parse.quote() in Python) to ensure all special characters are encoded correctly.
Examples
❌ Illegal pipe character in the query string
This is a common pattern seen with Google Fonts URLs that use | to separate font families:
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans|Roboto">
✅ Pipe character percent-encoded
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans%7CRoboto">
❌ Square brackets in the query string
Some APIs or frameworks use bracket notation in query parameters:
<link rel="stylesheet" href="https://example.com/styles?themes[]=dark&themes[]=compact">
✅ Square brackets percent-encoded
<link rel="stylesheet" href="https://example.com/styles?themes%5B%5D=dark&themes%5B%5D=compact">
❌ Space character in the query string
<link rel="stylesheet" href="https://example.com/css?file=my styles.css">
✅ Space character percent-encoded
<link rel="stylesheet" href="https://example.com/css?file=my%20styles.css">
Note that for Google Fonts specifically, the modern API (v2) uses a different URL format that avoids the pipe character altogether. Where possible, consider updating to the latest version of an API rather than just encoding the old URL.
The http-equiv attribute contains a misspelled or invalid value.
The http-equiv attribute on the <meta> element acts as a pragma directive, simulating an HTTP response header. The HTML specification only allows a specific set of values for this attribute. The valid values include content-type, default-style, refresh, X-UA-Compatible, and content-security-policy.
The value X-UA-Compatible was historically used to tell Internet Explorer to use a specific rendering engine. While it's largely obsolete now that IE is no longer supported, the HTML spec still recognizes it as a valid value.
Common mistakes that trigger this error include:
- Typos like
X-UA-Complatible,X-UA-Compatable, orX-UA-Compatble - Using non-standard values like
imagetoolbar,cleartype, orcache-control - Using values that were valid in older HTML versions but are no longer recognized
HTML Examples
❌ Invalid: misspelled value
<meta http-equiv="X-UA-Complatible" content="IE=edge,chrome=1" />
✅ Valid: correctly spelled value
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
Note that the chrome=1 portion referenced Google Chrome Frame, a discontinued plugin. It's safe to remove it along with the entire <meta> tag if you no longer need to support Internet Explorer.
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
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="1stPattern" width="10" height="10" patternUnits="userSpaceOnUse">
<circle cx="5" cy="5" r="3" fill="blue" />
</pattern>
</defs>
<rect width="200" height="200" fill="url(#1stPattern)" />
</svg>
❌ Invalid: id starts with a hyphen
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="-stripe-bg" width="10" height="10" patternUnits="userSpaceOnUse">
<rect width="5" height="10" fill="red" />
</pattern>
</defs>
<rect width="200" height="200" fill="url(#-stripe-bg)" />
</svg>
❌ Invalid: id contains special characters
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="my pattern!" width="10" height="10" patternUnits="userSpaceOnUse">
<circle cx="5" cy="5" r="3" fill="green" />
</pattern>
</defs>
<rect width="200" height="200" fill="url(#my pattern!)" />
</svg>
✅ Valid: id starts with a letter
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="firstPattern" width="10" height="10" patternUnits="userSpaceOnUse">
<circle cx="5" cy="5" r="3" fill="blue" />
</pattern>
</defs>
<rect width="200" height="200" fill="url(#firstPattern)" />
</svg>
✅ Valid: id starts with an underscore
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="_stripe-bg" width="10" height="10" patternUnits="userSpaceOnUse">
<rect width="5" height="10" fill="red" />
</pattern>
</defs>
<rect width="200" height="200" fill="url(#_stripe-bg)" />
</svg>
✅ Valid: Using letters, digits, hyphens, and underscores
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="dot-grid_v2" width="10" height="10" patternUnits="userSpaceOnUse">
<circle cx="5" cy="5" r="3" fill="green" />
</pattern>
</defs>
<rect width="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
<html lang="emg">
❌ Invalid: Country code used instead of language code
<html lang="us">
❌ Invalid: Incorrect region subtag
<html lang="en-UK">
❌ Invalid: Made-up language code
<html lang="english">
✅ Valid: Correct two-letter language code
<html lang="en">
✅ Valid: Language with region subtag
<html lang="en-GB">
✅ Valid: Full document with proper lang attribute
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="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.
A lang value follows the BCP 47 format, where a four-letter subtag placed after the primary language is read as a script subtag from the ISO 15924 registry, and the validator reports "Bad script subtag" when that four-letter piece is not a registered script code.
In es-TRAD, the es part is a valid language subtag for Spanish, but TRAD has four letters, so the parser treats it as a script subtag. ISO 15924 has no Trad script, so the value fails validation. This usually happens when someone invents a code to express a "traditional" or regional variant instead of using a registered subtag.
Valid script subtags are exactly four letters with an initial capital, such as Latn (Latin), Cyrl (Cyrillic), Hans (Simplified Han), and Hant (Traditional Han). Most languages do not need one, because each language has a default script. You add a script subtag only when the text uses a script other than the default, as in zh-Hant for Traditional Chinese.
To fix the value, either use a registered ISO 15924 script subtag or drop the extra subtag and keep just the language code, with an optional region.
Invalid example
<span lang="es-TRAD">Español tradicional</span>
TRAD is read as a script subtag, and it is not a valid ISO 15924 code.
Valid example
<span lang="es">Español</span>
When you genuinely need to mark a script, use a registered ISO 15924 subtag:
<span lang="zh-Hant">繁體中文</span>
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 -->
<img src="photo.jpg" alt="A sunset over the ocean" loading="1">
<!-- Boolean-style value -->
<img src="photo.jpg" alt="A sunset over the ocean" loading="true">
<!-- Misspelled value -->
<img src="photo.jpg" alt="A sunset over the ocean" loading="laxy">
<!-- Wrong case -->
<img src="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:
<img src="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:
<img src="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:
<img src="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 -->
<img src="hero-banner.jpg" alt="Welcome to our store" loading="eager">
<!-- Product images further down the page: defer loading -->
<img src="product-1.jpg" alt="Running shoes" loading="lazy">
<img src="product-2.jpg" alt="Hiking boots" loading="lazy">
<img src="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 -->
<link rel="stylesheet" href="responsive.css" media="max-width: 600px">
Correct: parentheses around media feature
<link rel="stylesheet" href="responsive.css" media="(max-width: 600px)">
Incorrect: typo in media type
<!-- ❌ Parse error: "scrren" is not a valid media type -->
<link rel="stylesheet" href="styles.css" media="scrren">
Correct: valid media type
<link rel="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 -->
<link rel="stylesheet" href="styles.css" media="screen or print">
Correct: comma-separated media query list
<link rel="stylesheet" href="styles.css" media="screen, print">
Incorrect: stray semicolon in the value
<!-- ❌ Parse error: semicolons are not valid in media queries -->
<link rel="stylesheet" href="styles.css" media="screen; (max-width: 768px)">
Correct: combining media type and feature with and
<link rel="stylesheet" href="styles.css" media="screen and (max-width: 768px)">
More valid media query examples
<!-- Simple media type -->
<link rel="stylesheet" href="print.css" media="print">
<!-- Apply to all media (default behavior, but explicit) -->
<link rel="stylesheet" href="base.css" media="all">
<!-- Multiple conditions with "and" -->
<link rel="stylesheet" href="tablet.css" media="screen and (min-width: 768px) and (max-width: 1024px)">
<!-- Using "not" to exclude a media type -->
<link rel="stylesheet" href="no-print.css" media="not print">
<!-- Multiple queries separated by commas (logical OR) -->
<link rel="stylesheet" href="special.css" media="(max-width: 600px), (orientation: portrait)">
<!-- Prefers-color-scheme for dark mode -->
<link rel="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:
<link rel="stylesheet" href="style.css" media="screen, projection">
Using projection as the sole media type:
<link rel="stylesheet" href="slides.css" media="projection">
Correct
Remove projection and keep the valid screen type:
<link rel="stylesheet" href="style.css" media="screen">
Replace projection with screen, since projectors are handled as screens:
<link rel="stylesheet" href="slides.css" media="screen">
Target both screens and print:
<link rel="stylesheet" href="style.css" media="screen, print">
If the stylesheet should apply to all devices, omit the media attribute (which defaults to all):
<link rel="stylesheet" href="style.css">
Or explicitly set it to all:
<link rel="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:
<link rel="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
<link rel="stylesheet" href="mobile.css" media="handheld">
The handheld media type is deprecated and will trigger the validation error.
Incorrect: misspelled media type
<link rel="stylesheet" href="styles.css" media="screen">
Incorrect: malformed media query
<link rel="stylesheet" href="responsive.css" media="max-width: 768px">
The feature expression is missing its surrounding parentheses.
Correct: using valid media types
<link rel="stylesheet" href="general.css">
<link rel="stylesheet" href="print.css" media="print">
<link rel="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:
<link rel="stylesheet" href="mobile.css" media="screen and (max-width: 768px)">
Correct: using complex media queries
<link rel="stylesheet" href="dark.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="portrait.css" media="screen and (orientation: portrait)">
<link rel="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 -->
<input type="file" name="attachments" multiple="1">
<!-- Bad: "true" is not a valid boolean attribute value -->
<input type="email" name="recipients" multiple="true">
Invalid: multiple on an unsupported input type
<!-- Bad: type="text" does not support the multiple attribute -->
<input type="text" name="tags" multiple>
<!-- Bad: type="number" does not support the multiple attribute -->
<input type="number" name="quantities" multiple>
Valid: correct usage of multiple
<!-- Correct: boolean attribute with no value -->
<input type="file" name="attachments" multiple>
<!-- Correct: empty string value (valid boolean syntax) -->
<input type="email" name="recipients" multiple="">
<!-- Correct: attribute name as value (valid for XHTML compatibility) -->
<input type="file" name="documents" multiple="multiple">
Full corrected document
<!DOCTYPE html>
<html lang="en">
<head>
<title>Upload Form</title>
</head>
<body>
<form action="/submit" method="post" enctype="multipart/form-data">
<label for="recipients">Recipients:</label>
<input type="email" id="recipients" name="recipients" multiple placeholder="a@example.com, b@example.com">
<label for="files">Attachments:</label>
<input type="file" id="files" name="files" multiple>
<button type="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
<iframe src="https://example.com" name="_example"></iframe>
<a href="https://example.com/page" target="_example">Open in frame</a>
The name _example starts with an underscore, which makes it invalid. A browser might interpret _example unpredictably or ignore the name entirely when used as a target.
✅ Fixed: underscore removed from the start
<iframe src="https://example.com" name="example"></iframe>
<a href="https://example.com/page" target="example">Open in frame</a>
✅ Fixed: underscore used elsewhere in the name
<iframe src="https://example.com" name="my_example"></iframe>
<a href="https://example.com/page" target="my_example">Open in frame</a>
Underscores are perfectly fine as long as they aren't the first character.
❌ Invalid: using a reserved keyword as a frame name
<iframe src="https://example.com" name="_blank"></iframe>
Using _blank as an <iframe> name is also invalid because it's a reserved browsing context keyword. A link targeting _blank would open in a new window/tab rather than inside this <iframe>, which is almost certainly not what you intended.
✅ Fixed: descriptive name without underscore prefix
<iframe src="https://example.com" name="content-frame"></iframe>
<a href="https://example.com/page" target="content-frame">Open in frame</a>
How to fix
- Find every
<iframe>element 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
<form method="post" novalidate="true">
<label>Email:
<input type="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:
<form method="post" novalidate="1">
<form method="post" novalidate="yes">
<form method="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)
<form method="post" novalidate>
<label>Email:
<input type="email" name="email" required>
</label>
<button>Submit</button>
</form>
✅ Valid: Empty string value
<form method="post" novalidate="">
<label>Email:
<input type="email" name="email" required>
</label>
<button>Submit</button>
</form>
✅ Valid: Value matching the attribute name
<form method="post" novalidate="novalidate">
<label>Email:
<input type="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:
<form method="post">
<label>Email:
<input type="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:
<form method="post">
<label>Email:
<input type="email" name="email" required>
</label>
<button>Submit</button>
<button formnovalidate>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
<a href="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
<a href="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
<a href="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
<a href="https://example.com" ping="https://example.com/track">Visit Example</a>
Correct: multiple space-separated absolute URLs
<a href="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.
<video controls poster="/img/video images/snapshot.png">
<source src="/videos/sample.mp4" type="video/mp4">
</video>
Incorrect — space in the filename
The filename my poster.jpg also triggers the same error.
<video controls poster="/img/my poster.jpg">
<source src="/videos/sample.mp4" type="video/mp4">
</video>
Fixed — percent-encoding the spaces
Each space is replaced with %20, producing a valid URL.
<video controls poster="/img/video%20images/snapshot.png">
<source src="/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.
<video controls poster="/img/video-images/snapshot.png">
<source src="/videos/sample.mp4" type="video/mp4">
</video>
Fixed — removing spaces from the filename
<video controls poster="/img/my-poster.jpg">
<source src="/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.
Square brackets are not valid characters in a URL path segment and must be percent-encoded in the poster attribute of a <video> element.
The poster attribute accepts a valid URL that points to an image displayed while the video is not playing or until the first frame loads. URLs follow RFC 3986, which restricts the characters allowed in path segments. Square brackets ([ and ]) are reserved characters that are only permitted in the host component of a URL (for IPv6 addresses). When they appear in a path, query, or fragment component, they must be percent-encoded as %5B for [ and %5D for ].
This applies to any HTML attribute that expects a URL, not just poster. The same rule covers src, href, action, and similar attributes.
Invalid example
<video poster="/images/thumbnail[1].jpg" controls>
<source src="video.mp4" type="video/mp4">
</video>
Fixed example
Replace [ with %5B and ] with %5D:
<video poster="/images/thumbnail%5B1%5D.jpg" controls>
<source src="video.mp4" type="video/mp4">
</video>
If you control the file names on the server, renaming the file to avoid square brackets entirely is a simpler long term fix:
<video poster="/images/thumbnail-1.jpg" controls>
<source src="video.mp4" type="video/mp4">
</video>
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
<link rel="styleshet" href="main.css">
The validator reports that styleshet is not a recognized keyword and is not an absolute URL.
Correct: Fixed spelling
<link rel="stylesheet" href="main.css">
Incorrect: Non-standard keyword on an anchor
<a href="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
<a href="https://example.com" rel="noopener">Visit Example</a>
Incorrect: Relative URL as a custom link type
<link rel="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:
<link rel="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 -->
<link rel="stylesheet" href="styles.css">
<!-- Linking a favicon -->
<link rel="icon" href="favicon.ico">
<!-- Preloading a resource -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
<!-- Telling search engines not to follow a link -->
<a href="https://example.com" rel="nofollow">Sponsored link</a>
<!-- Opening a link safely in a new tab -->
<a href="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 -->
<a href="https://example.com" target="_blank" rel="noopener noreferrer">External</a>
<!-- Incorrect: "popup" is not a recognized keyword or absolute URL -->
<a href="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:
<input type="text" required="true">
<input type="email" required="false">
<input type="text" required="yes">
<input type="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) -->
<input type="text" required>
<!-- Also valid: empty string -->
<input type="text" required="">
<!-- Also valid: canonical name as value -->
<input type="text" required="required">
Correct: Making a field optional
To make a field optional, remove the attribute entirely:
<input type="text">
Full form example
<form action="/submit" method="post">
<label for="name">Name (required):</label>
<input type="text" id="name" name="name" required>
<label for="notes">Notes (optional):</label>
<input type="text" id="notes" name="notes">
<button type="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"
>
A sizes attribute on an <img> element contains a media condition where a number is missing its CSS unit (like px, em, or rem).
The sizes attribute tells the browser how wide an image will be displayed at different viewport sizes, so it can pick the best source from a srcset. Each entry in sizes is a media condition paired with a length value. The media conditions follow standard CSS media query syntax, which means all non-zero numeric values must include a unit.
A common mistake is writing something like (max-width: 600) instead of (max-width: 600px). In CSS, bare numbers without units are invalid except for 0, which doesn't need a unit because zero pixels, zero ems, and zero rems are all the same thing.
Example with the error
<img
src="photo.jpg"
srcset="photo-480.jpg 480w, photo-800.jpg 800w"
sizes="(max-width: 600) 480px, 800px"
alt="A sunset over the ocean">
The media condition (max-width: 600) is missing a unit after 600.
Fixed example
<img
src="photo.jpg"
srcset="photo-480.jpg 480w, photo-800.jpg 800w"
sizes="(max-width: 600px) 480px, 800px"
alt="A sunset over the ocean">
Adding px (or whichever unit is appropriate) to the number in the media condition resolves the error. This applies to every numeric value in the media condition, not just the length that follows it. For example, (min-width: 40em) 50vw, 100vw is also valid.
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.
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