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 lang attribute on any HTML element must be a valid IANA language subtag (like en, fr, or ja), not a programming language name.
The lang attribute specifies the natural (human) language of the element's content, used by browsers, screen readers, and search engines for accessibility and localization purposes. Values like csharp, javascript, or python are not valid because they are not human languages registered in the IANA Language Subtag Registry.
This issue commonly arises when using <code lang="csharp"> to indicate the programming language of a code snippet. While the intention makes sense, lang is not the right attribute for this purpose.
To indicate a programming language on a <code> element, the recommended convention from the HTML specification is to use the class attribute with a language- prefix (e.g., class="language-csharp"). This is also the pattern used by popular syntax highlighting libraries like Prism.js and highlight.js.
If the code content is written in a specific human language (like English), you can still use lang="en" alongside the class.
Bad Example
<pre>
<codelang="csharp">
Console.WriteLine("Hello, World!");
</code>
</pre>
Good Example
<pre>
<codeclass="language-csharp">
Console.WriteLine("Hello, World!");
</code>
</pre>
The X-UA-Compatible meta tag was originally introduced to control which rendering engine Internet Explorer would use to display a page. Developers could force IE to emulate older versions (e.g., IE=7, IE=9) or use the latest available engine with IE=edge. The value IE=edge,chrome=1 was also commonly used to activate the Google Chrome Frame plugin, which allowed Internet Explorer to use Chrome's rendering engine instead.
The HTML specification now only permits the value IE=edge for this meta tag. Other values are considered invalid for several reasons:
- Google Chrome Frame is discontinued. The
chrome=1directive targeted a plugin that was retired in February 2014 and is no longer supported by any browser. - Legacy IE rendering modes are obsolete. Internet Explorer itself has been retired, making emulation modes like
IE=EmulateIE7orIE=9pointless. - Standards compliance. The WHATWG HTML living standard explicitly requires the
contentattribute value to beIE=edgewhenhttp-equiv="X-UA-Compatible"is used.
In practice, since all modern browsers use their latest rendering engine by default, this meta tag has little functional impact today. If your site no longer needs to support Internet Explorer at all, you can safely remove the tag entirely. If you choose to keep it — for example, in environments where legacy IE browsers might still access your site — ensure the value is exactly IE=edge.
Examples
Invalid: Using chrome=1 with IE=edge
This was a common pattern when Google Chrome Frame was active, but it now triggers a validation error:
<metahttp-equiv="X-UA-Compatible"content="IE=edge,chrome=1">
Invalid: Using a legacy IE rendering mode
Forcing a specific IE version is no longer valid:
<metahttp-equiv="X-UA-Compatible"content="IE=EmulateIE7">
Invalid: Specifying a particular IE version
<metahttp-equiv="X-UA-Compatible"content="IE=9">
Valid: Using IE=edge
The only accepted value is IE=edge:
<metahttp-equiv="X-UA-Compatible"content="IE=edge">
Valid: Removing the tag entirely
If you don't need Internet Explorer compatibility, the simplest fix is to remove the meta tag altogether. A minimal valid document without it:
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
The listbox role is the implicit ARIA role for a <select> element only when it has a multiple attribute or a size attribute greater than 1. A standard single-selection <select> (dropdown) has an implicit role of combobox, so explicitly assigning role="listbox" to it creates a conflict.
When a <select> element has no multiple attribute and no size greater than 1, browsers render it as a collapsed dropdown — a combobox. The listbox role describes a widget where all options are persistently visible, which matches the behavior of a multi-select or a select with a visible size greater than 1. Applying role="listbox" to a standard dropdown misrepresents the control to assistive technologies.
You have a few options to fix this: remove the role="listbox" entirely (since the browser already assigns the correct implicit role), add the multiple attribute, or set size to a value greater than 1.
Incorrect Example
<selectrole="listbox"name="color">
<optionvalue="red">Red</option>
<optionvalue="blue">Blue</option>
<optionvalue="green">Green</option>
</select>
Fixed Examples
Remove the explicit role and let the browser handle it:
<selectname="color">
<optionvalue="red">Red</option>
<optionvalue="blue">Blue</option>
<optionvalue="green">Green</option>
</select>
Or, if you genuinely need role="listbox", use multiple or size greater than 1:
<selectrole="listbox"name="color"multiple>
<optionvalue="red">Red</option>
<optionvalue="blue">Blue</option>
<optionvalue="green">Green</option>
</select>
<selectrole="listbox"name="color"size="3">
<optionvalue="red">Red</option>
<optionvalue="blue">Blue</option>
<optionvalue="green">Green</option>
</select>
In most cases, simply removing role="listbox" is the best fix. The implicit ARIA roles already convey the correct semantics to assistive technologies without any extra attributes.
The lang attribute on the <html> element declares the primary language of the document's content. When this attribute is left empty (lang=""), it effectively tells browsers and assistive technologies that the language is unknown or intentionally unspecified — which is almost never what you want.
This matters for several important reasons:
- Accessibility: Screen readers rely on the
langattribute to select the correct pronunciation engine. An empty value can cause a screen reader to fall back to a default language, potentially reading English text with incorrect pronunciation rules. - Search engines: Search engines use the
langattribute to understand what language your content is in, which helps serve your pages to the right audience. - Browser features: Browsers use the language declaration for hyphenation, spell-checking, font selection, and other language-sensitive rendering decisions.
- Standards compliance: The WHATWG HTML living standard specifies that if the
langattribute is present, its value must be a valid BCP 47 language tag. An empty string is not a valid language tag.
The fix is straightforward: set the lang attribute to a valid BCP 47 language tag that matches your content. For English, common values include en (general English), en-US (American English), or en-GB (British English). If your content is in another language, use the appropriate tag (e.g., fr for French, de for German, ja for Japanese).
Examples
❌ Empty lang attribute (triggers the warning)
<!DOCTYPE html>
<htmllang="">
<head>
<title>My Page</title>
</head>
<body>
<h1>Welcome to my website</h1>
</body>
</html>
❌ Missing lang attribute entirely
While a missing lang attribute triggers a different warning, it causes the same underlying problem — no language is declared:
<!DOCTYPE html>
<html>
<head>
<title>My Page</title>
</head>
<body>
<h1>Welcome to my website</h1>
</body>
</html>
✅ Correct: specifying the language
<!DOCTYPE html>
<htmllang="en">
<head>
<title>My Page</title>
</head>
<body>
<h1>Welcome to my website</h1>
</body>
</html>
✅ Correct: using a regional variant
<!DOCTYPE html>
<htmllang="en-US">
<head>
<title>My Page</title>
</head>
<body>
<h1>Welcome to my website</h1>
</body>
</html>
Using lang for mixed-language content
If your document is primarily in English but contains sections in other languages, set lang="en" on the <html> element and override it on specific elements:
<!DOCTYPE html>
<htmllang="en">
<head>
<title>Multilingual Page</title>
</head>
<body>
<h1>Welcome</h1>
<p>This page contains a quote in French:</p>
<blockquotelang="fr">
<p>La vie est belle.</p>
</blockquote>
</body>
</html>
The <picture> element exists to give browsers a choice between multiple image sources. The browser evaluates each <source> in order, looking for the first one whose conditions match the current environment. These conditions are expressed through the media attribute (e.g., viewport width or resolution) and the type attribute (e.g., image/webp or image/avif). If a <source> lacks both attributes, it acts as an unconditional match — the browser will always select it, making any subsequent <source> elements or an <img> with srcset unreachable. This defeats the purpose of the <picture> element entirely.
The HTML specification requires these attributes specifically to prevent this situation. When a <source> has a following sibling <source> or a following <img> with srcset, at least one selection criterion (media or type) must be present so the browser can meaningfully choose between the options. A <source> without these attributes is only valid if it's the last <source> before a plain <img> (one without srcset), since in that case it serves as the final fallback within the <picture>.
This matters for several reasons:
- Standards compliance: The HTML living standard explicitly defines this requirement. Violating it produces a validation error.
- Predictable rendering: Without distinguishing attributes, browsers may silently ignore sources or always pick the first one, leading to inconsistent behavior across browsers.
- Performance: The
<picture>element is often used to serve smaller images on small viewports or modern formats like WebP and AVIF to browsers that support them. Without propermediaortypeattributes, these optimizations won't work as intended.
How to fix it
Add a media attribute, a type attribute, or both to each <source> element that is followed by another <source> or an <img> with srcset:
- Use
typewhen you're offering the same image in different formats (e.g., AVIF, WebP, JPEG). The browser picks the first format it supports. - Use
mediawhen you're serving different images based on viewport conditions (art direction). The browser picks the source whose media query matches. - Use both when you want to combine format negotiation with art direction.
Examples
Incorrect — <source> without media or type
Each <source> below has no selection criterion, so the browser has no way to choose between them:
<picture>
<sourcesrcset="hero.webp">
<sourcesrcset="hero.jpg">
<imgsrc="hero.jpg"srcset="hero-2x.jpg 2x"alt="A mountain landscape">
</picture>
Correct — using type for format negotiation
Adding type lets the browser pick the first format it supports:
<picture>
<sourcesrcset="hero.avif"type="image/avif">
<sourcesrcset="hero.webp"type="image/webp">
<imgsrc="hero.jpg"srcset="hero-2x.jpg 2x"alt="A mountain landscape">
</picture>
Correct — using media for art direction
Adding media lets the browser pick the source that matches the viewport:
<picture>
<sourcesrcset="hero-wide.jpg"media="(min-width: 1024px)">
<sourcesrcset="hero-narrow.jpg"media="(max-width: 1023px)">
<imgsrc="hero-narrow.jpg"srcset="hero-narrow-2x.jpg 2x"alt="A mountain landscape">
</picture>
Correct — combining media and type
You can use both attributes together to serve the right format at the right viewport size:
<picture>
<sourcesrcset="hero-wide.avif"media="(min-width: 1024px)"type="image/avif">
<sourcesrcset="hero-wide.webp"media="(min-width: 1024px)"type="image/webp">
<sourcesrcset="hero-narrow.avif"media="(max-width: 1023px)"type="image/avif">
<sourcesrcset="hero-narrow.webp"media="(max-width: 1023px)"type="image/webp">
<imgsrc="hero-narrow.jpg"alt="A mountain landscape">
</picture>
Correct — single <source> before a plain <img>
When there's only one <source> and the <img> has no srcset, no media or type is required — but adding type is still recommended for clarity:
<picture>
<sourcesrcset="hero.webp"type="image/webp">
<imgsrc="hero.jpg"alt="A mountain landscape">
</picture>
The sandbox attribute applies a strict set of restrictions to content loaded inside an <iframe>. By default, sandboxed iframes cannot run scripts, submit forms, open popups, or access storage. You can selectively lift specific restrictions by adding recognized keywords like allow-scripts, allow-forms, allow-popups, allow-same-origin, and others defined in the WHATWG HTML standard.
The keyword allow-storage-access-by-user-activation was proposed as a way to let sandboxed iframes request access to first-party storage (such as cookies) after a user gesture. However, this keyword was never adopted into the HTML specification. The functionality it aimed to provide is now handled by the Storage Access API, which uses document.requestStorageAccess() and document.hasStorageAccess() in JavaScript. Because the keyword was never standardized, the W3C validator correctly flags it as invalid.
Why this matters
- Standards compliance: Using non-standard keywords means your HTML doesn't conform to the specification, which the validator will flag as an error.
- Browser inconsistency: Since this keyword was experimental and never standardized, browser support is unreliable. Some browsers may silently ignore it, while others may have briefly supported it before removing it.
- False sense of security: Including an unrecognized sandbox keyword doesn't actually enable the behavior you expect. The iframe won't gain storage access just because this keyword is present—the browser simply ignores unknown tokens.
How to fix it
- Remove the invalid keyword from the
sandboxattribute. - Keep any other valid sandbox keywords that your iframe needs.
- Use the Storage Access API in JavaScript within the iframe if you need cross-site storage access. The embedded page must call
document.requestStorageAccess()in response to a user gesture, and thesandboxattribute must includeallow-scriptsandallow-same-originfor this API to work.
Examples
❌ Invalid: using a non-standard sandbox keyword
<iframe
src="https://example.com/widget"
sandbox="allow-scripts allow-same-origin allow-storage-access-by-user-activation">
</iframe>
✅ Valid: removing the non-standard keyword
<iframe
src="https://example.com/widget"
sandbox="allow-scripts allow-same-origin">
</iframe>
The embedded page at https://example.com/widget can then use the Storage Access API in JavaScript:
document.querySelector('#login-button').addEventListener('click',async()=>{
consthasAccess=awaitdocument.hasStorageAccess();
if(!hasAccess){
awaitdocument.requestStorageAccess();
}
// Storage (cookies, etc.) is now accessible
});
✅ Valid: sandbox with other standard keywords
If your iframe doesn't need storage access at all, simply use the standard keywords you require:
<iframe
src="https://example.com/form"
sandbox="allow-scripts allow-forms allow-popups">
</iframe>
Note that for document.requestStorageAccess() to work inside a sandboxed iframe, you must include both allow-scripts (so JavaScript can run) and allow-same-origin (so the iframe retains its origin). Without these, the Storage Access API calls will fail.
The srcset attribute supports two types of descriptors: width descriptors (like 480w) and pixel density descriptors (like 2x). However, these two types cannot be mixed, and the sizes attribute is only compatible with width descriptors. The sizes attribute tells the browser how wide the image will be displayed at various viewport sizes, and the browser uses this information along with the width descriptors in srcset to choose the most appropriate image file. If sizes is present but an image candidate lacks a width descriptor, the browser cannot perform this calculation correctly.
This matters for several reasons. First, it violates the WHATWG HTML specification, which explicitly requires that when sizes is present, all image candidates must use width descriptors. Second, browsers may ignore malformed srcset values or fall back to unexpected behavior, resulting in the wrong image being loaded — potentially hurting performance by downloading unnecessarily large files or degrading visual quality by selecting a too-small image. Third, standards-compliant markup ensures consistent, predictable behavior across all browsers and devices.
A common mistake is specifying a plain URL without any descriptor, or mixing density descriptors (1x, 2x) with the sizes attribute. An image candidate string without any descriptor defaults to 1x, which is a density descriptor — and that conflicts with the presence of sizes.
Examples
❌ Incorrect: Missing width descriptor with sizes present
<picture>
<source
srcset="image-small.jpg, image-large.jpg 1024w"
sizes="(max-width: 600px) 480px, 800px">
<imgsrc="image-fallback.jpg"alt="A scenic landscape">
</picture>
Here, image-small.jpg has no width descriptor. Since sizes is present, this triggers the validation error.
❌ Incorrect: Using density descriptors with sizes
<img
srcset="image-1x.jpg 1x, image-2x.jpg 2x"
sizes="(max-width: 600px) 480px, 800px"
src="image-fallback.jpg"
alt="A scenic landscape">
Density descriptors (1x, 2x) are incompatible with the sizes attribute.
✅ Correct: All candidates have width descriptors
<picture>
<source
srcset="image-small.jpg 480w, image-large.jpg 1024w"
sizes="(max-width: 600px) 480px, 800px">
<imgsrc="image-fallback.jpg"alt="A scenic landscape">
</picture>
Every image candidate now includes a width descriptor, which pairs correctly with the sizes attribute.
✅ Correct: Using density descriptors without sizes
If you want to use density descriptors instead of width descriptors, simply remove the sizes attribute:
<img
srcset="image-1x.jpg 1x, image-2x.jpg 2x"
src="image-fallback.jpg"
alt="A scenic landscape">
This is valid because density descriptors don't require (and shouldn't be used with) the sizes attribute.
✅ Correct: Width descriptors on <img> with sizes
<img
srcset="photo-320.jpg 320w, photo-640.jpg 640w, photo-1280.jpg 1280w"
sizes="(max-width: 400px) 320px, (max-width: 800px) 640px, 1280px"
src="photo-640.jpg"
alt="A close-up of a flower">
Each entry in srcset specifies its intrinsic width, and sizes tells the browser which display width to expect at each breakpoint. The browser then selects the best-fitting image automatically.
The srcset attribute allows you to provide multiple image sources so the browser can choose the most appropriate one based on the user's viewport size or screen density. There are two distinct modes for srcset:
- Width descriptor mode — each candidate specifies its intrinsic width using a
wdescriptor (e.g.,400w). This mode requires thesizesattribute so the browser knows how much space the image will occupy in the layout and can calculate which source to download. - Pixel density descriptor mode — each candidate specifies a pixel density using an
xdescriptor (e.g.,2x). This mode must not include asizesattribute.
When you include a sizes attribute but forget to add width descriptors to one or more srcset entries, the browser has incomplete information. The HTML specification explicitly states that if sizes is present, all image candidate strings must use width descriptors. An entry without a descriptor defaults to 1x (a pixel density descriptor), which conflicts with the width descriptor mode triggered by sizes. This mismatch causes the W3C validator to report the error.
Beyond validation, this matters for real-world performance. Responsive images are one of the most effective tools for reducing page weight on smaller screens. If the descriptors are missing or mismatched, browsers may download an image that is too large or too small, hurting both performance and visual quality.
How to fix it
You have two options depending on your use case:
Option 1: Add width descriptors to all srcset candidates
If you need the browser to select images based on viewport size (the most common responsive images pattern), keep the sizes attribute and ensure every srcset entry has a w descriptor that matches the image's intrinsic pixel width.
Option 2: Remove sizes and use pixel density descriptors
If you only need to serve higher-resolution images for high-DPI screens (e.g., Retina displays) and the image always renders at the same CSS size, remove the sizes attribute and use x descriptors instead.
Examples
❌ Incorrect: sizes present but srcset entry has no width descriptor
<img
src="photo-800.jpg"
srcset="photo-400.jpg, photo-800.jpg"
sizes="(min-width: 600px) 800px, 100vw"
alt="A mountain landscape">
Both srcset entries lack a width descriptor. Because sizes is present, the validator reports an error for each candidate.
✅ Correct: sizes present with width descriptors on every candidate
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="(min-width: 600px) 800px, 100vw"
alt="A mountain landscape">
Each candidate now specifies its intrinsic width (400w and 800w), which tells the browser the actual pixel width of each source file. The browser combines this with the sizes value to pick the best match.
❌ Incorrect: mixing width descriptors and bare entries
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg"
sizes="(min-width: 600px) 800px, 100vw"
alt="A mountain landscape">
The second candidate (photo-800.jpg) is missing its width descriptor. All candidates must have one when sizes is present — not just some of them.
✅ Correct: pixel density descriptors without sizes
<img
src="photo-800.jpg"
srcset="photo-800.jpg 1x, photo-1600.jpg 2x"
alt="A mountain landscape">
Here the sizes attribute is removed, and each srcset entry uses a pixel density descriptor (1x, 2x). This is valid and appropriate when the image always occupies the same CSS dimensions regardless of viewport width.
❌ Incorrect: using sizes with pixel density descriptors
<img
src="photo-800.jpg"
srcset="photo-800.jpg 1x, photo-1600.jpg 2x"
sizes="(min-width: 600px) 800px, 100vw"
alt="A mountain landscape">
The sizes attribute and x descriptors cannot be combined. Either switch to w descriptors or remove sizes.
Quick reference
| Pattern | srcset descriptor | sizes required? |
|---|---|---|
| Viewport-based selection | Width (w) | Yes |
| Density-based selection | Pixel density (x) | No — must be omitted |
Remember that the w value in srcset refers to the image file's intrinsic pixel width (e.g., an 800-pixel-wide image gets 800w), while values in sizes use CSS length units like px, vw, or em to describe how wide the image will render in the layout.
The HTML specification enforces this rule because a required <select> element needs options for the user to choose from. Without any <option> children, the element is semantically meaningless — it's a dropdown with nothing to select, yet the form demands a selection before submission. This creates an impossible situation for the user and an ambiguous state for the browser.
This rule specifically applies when all three of these conditions are true:
- The
requiredattribute is present. - The
multipleattribute is not present. - The
sizeattribute is either absent or set to1(the default for a single-selection<select>).
When multiple is set or size is greater than 1, the <select> behaves differently (as a list box rather than a dropdown), and the specification relaxes this constraint. But for the standard single-selection dropdown, at least one <option> is mandatory.
Why this matters
- Usability: A required dropdown with no options gives users no way to satisfy the form requirement, effectively blocking form submission entirely.
- Accessibility: Screen readers announce
<select>elements along with their available options. An empty required dropdown creates a confusing experience for assistive technology users. - Form validation: Browsers use the first
<option>with an emptyvalueas the placeholder. The built-in constraint validation forrequiredselects relies on checking whether the selected option's value is a non-empty string. Without any options, this validation behavior is undefined.
How the placeholder pattern works
The HTML specification defines a specific behavior for required single-selection dropdowns: the first <option> element, if it has an empty value attribute, acts as a placeholder. When the form is submitted with this placeholder still selected, browser validation will reject the submission because the value is empty. This is the standard pattern for prompting users to make a deliberate choice.
Examples
❌ Invalid: required <select> with no options
<labelfor="color">Pick a color:</label>
<selectid="color"name="color"required>
</select>
This triggers the validation error because there are no <option> elements inside the required <select>.
❌ Invalid: required <select> with only a group but no options
<labelfor="color">Pick a color:</label>
<selectid="color"name="color"required>
<optgrouplabel="Colors">
</optgroup>
</select>
An empty <optgroup> doesn't satisfy the requirement. The <select> still needs at least one <option>.
✅ Valid: required <select> with a placeholder and options
<labelfor="color">Pick a color:</label>
<selectid="color"name="color"required>
<optionvalue="">--Select a color--</option>
<optionvalue="red">Red</option>
<optionvalue="green">Green</option>
<optionvalue="blue">Blue</option>
</select>
The first <option> has an empty value, so it serves as a placeholder. The browser will require the user to choose one of the other options before submitting the form.
✅ Valid: required <select> with multiple (rule does not apply)
<labelfor="colors">Pick one or more colors:</label>
<selectid="colors"name="colors"requiredmultiple>
</select>
This does not trigger the error because the multiple attribute is present, which exempts the element from this particular rule. However, an empty multi-select is still poor UX and should generally be avoided.
✅ Valid: required <select> with size greater than 1 (rule does not apply)
<labelfor="colors">Pick a color:</label>
<selectid="colors"name="colors"requiredsize="4">
</select>
When size is greater than 1, the element renders as a list box and the rule no longer applies. Again, while technically valid, an empty list box isn't useful in practice.
✅ Valid: required <select> with grouped options
<labelfor="vehicle">Choose a vehicle:</label>
<selectid="vehicle"name="vehicle"required>
<optionvalue="">--Select a vehicle--</option>
<optgrouplabel="Cars">
<optionvalue="sedan">Sedan</option>
<optionvalue="suv">SUV</option>
</optgroup>
<optgrouplabel="Trucks">
<optionvalue="pickup">Pickup</option>
<optionvalue="semi">Semi</option>
</optgroup>
</select>
Options inside <optgroup> elements count as child options of the <select>, so this is fully valid.
The srcset attribute supports two types of descriptors: width descriptors (e.g., 480w) and pixel density descriptors (e.g., 2x). These two types cannot be mixed, and the sizes attribute is specifically designed to work with width descriptors. The sizes attribute tells the browser how wide the image will be displayed at various viewport sizes, so the browser can then pick the best image from srcset based on the widths you've provided. If any candidate in srcset lacks a width descriptor — or uses a density descriptor instead — the browser can't perform this calculation, and the HTML is invalid.
This matters for several reasons. First, browsers rely on the combination of sizes and width descriptors to make intelligent decisions about which image to download before the layout is computed. An invalid srcset can lead to the browser ignoring the entire attribute or selecting a suboptimal image, wasting bandwidth or displaying a blurry result. Second, standards compliance ensures consistent behavior across all browsers and devices.
A common mistake is specifying sizes while using density descriptors (1x, 2x) or providing bare URLs without any descriptor in srcset. If you want to use density descriptors, simply remove the sizes attribute. If you want responsive image selection based on viewport width, use width descriptors for every candidate.
Examples
Incorrect: Using density descriptors with sizes
<picture>
<source
srcset="image-small.jpg 1x, image-large.jpg 2x"
sizes="(max-width: 600px) 100vw, 50vw">
<imgsrc="image-small.jpg"alt="A landscape photo">
</picture>
This triggers the error because 1x and 2x are density descriptors, but the sizes attribute requires width descriptors.
Incorrect: Missing descriptor on one candidate
<picture>
<source
srcset="image-small.jpg, image-large.jpg 800w"
sizes="(max-width: 600px) 100vw, 50vw">
<imgsrc="image-small.jpg"alt="A landscape photo">
</picture>
Here, image-small.jpg has no descriptor at all. When sizes is present, every candidate must have a width descriptor.
Correct: All candidates use width descriptors with sizes
<picture>
<source
srcset="image-small.jpg 400w, image-large.jpg 800w"
sizes="(max-width: 600px) 100vw, 50vw">
<imgsrc="image-small.jpg"alt="A landscape photo">
</picture>
Each image candidate now specifies a width descriptor (400w, 800w), which matches the requirement imposed by the sizes attribute.
Correct: Using density descriptors without sizes
If you only need density-based selection (e.g., for retina displays) and don't need viewport-based sizing, remove the sizes attribute entirely:
<picture>
<sourcesrcset="image-small.jpg 1x, image-large.jpg 2x">
<imgsrc="image-small.jpg"alt="A landscape photo">
</picture>
Correct: Using srcset with width descriptors on <img>
The same rules apply when using srcset directly on an <img> element:
<img
srcset="photo-320.jpg 320w, photo-640.jpg 640w, photo-1024.jpg 1024w"
sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 33vw"
src="photo-640.jpg"
alt="A mountain landscape">
Every candidate in srcset includes a width descriptor, making this fully valid alongside the sizes attribute. The src attribute serves as the fallback for browsers that don't support srcset.
The srcset attribute allows you to provide multiple image sources so the browser can choose the most appropriate one based on the user's device and viewport. There are two distinct modes for srcset:
- Width descriptor mode — Each candidate specifies the image's intrinsic width using a
wdescriptor (e.g.,640w). This mode requires thesizesattribute so the browser knows how much space the image will occupy in the layout, enabling it to pick the best candidate. - Density descriptor mode — Each candidate specifies a pixel density using an
xdescriptor (e.g.,2x). This mode does not use thesizesattribute; the browser simply matches candidates to the device's pixel density.
These two modes are mutually exclusive. You cannot mix w and x descriptors in the same srcset, and you cannot use x descriptors (or bare URLs with no descriptor) when sizes is present. The HTML specification is explicit about this: if sizes is specified, all image candidate strings must include a width descriptor.
Why this matters
- Standards compliance: The WHATWG HTML Living Standard defines strict parsing rules for
srcset. Whensizesis present, the browser's source selection algorithm expects width descriptors. Providing density descriptors or bare candidates in this context violates the spec and produces undefined behavior. - Broken image selection: Browsers rely on the
sizesattribute to calculate whichw-described image best fits the current layout width. If you providexdescriptors alongsidesizes, the browser may ignore thesrcsetentirely or fall back to thesrcattribute, defeating the purpose of responsive images. - Accessibility and performance: Responsive images exist to serve appropriately sized files to different devices. An invalid
srcset/sizescombination can result in oversized images being downloaded on small screens (wasting bandwidth) or undersized images on large screens (reducing visual quality).
How to fix it
You have two options:
- Keep
sizesand switch to width descriptors — Replace everyxdescriptor (or missing descriptor) insrcsetwith the actual intrinsic pixel width of each image file, expressed with awsuffix. - Remove
sizesand keep density descriptors — If you only need to serve different resolutions for high-DPI screens at a fixed layout size, drop thesizesattribute and usexdescriptors.
When using width descriptors, the value must match the image file's actual intrinsic width in pixels. For example, if photo-640.jpg is 640 pixels wide, its descriptor should be 640w.
Examples
Invalid: sizes present with density descriptors
This triggers the error because 1x and 2x are density descriptors, but sizes requires width descriptors.
<img
src="photo-640.jpg"
srcset="photo-640.jpg 1x, photo-1280.jpg 2x"
sizes="(max-width: 600px) 100vw, 600px"
alt="A mountain landscape">
Invalid: sizes present with a bare candidate (no descriptor)
A candidate with no descriptor defaults to 1x, which is a density descriptor — still invalid when sizes is present.
<img
src="photo-640.jpg"
srcset="photo-640.jpg, photo-1280.jpg 2x"
sizes="(max-width: 600px) 100vw, 600px"
alt="A mountain landscape">
Fix: use width descriptors with sizes
Each candidate now specifies the intrinsic width of the image file. The browser uses the sizes value to determine which image to download.
<img
src="photo-640.jpg"
srcset="photo-320.jpg 320w, photo-640.jpg 640w, photo-1280.jpg 1280w"
sizes="(max-width: 600px) 100vw, 600px"
alt="A mountain landscape">
Alternative fix: remove sizes and use density descriptors
If you don't need the browser to choose images based on layout width — for example, the image always renders at a fixed CSS size — drop sizes and use x descriptors.
<img
src="photo-640.jpg"
srcset="photo-640.jpg 1x, photo-1280.jpg 2x"
alt="A mountain landscape">
Using width descriptors with source inside picture
The same rule applies to source elements inside a picture. If sizes is present, every candidate must use a w descriptor.
<picture>
<source
srcset="hero-480.webp 480w, hero-960.webp 960w, hero-1920.webp 1920w"
sizes="(max-width: 768px) 100vw, 50vw"
type="image/webp">
<img
src="hero-960.jpg"
srcset="hero-480.jpg 480w, hero-960.jpg 960w, hero-1920.jpg 1920w"
sizes="(max-width: 768px) 100vw, 50vw"
alt="A hero banner image">
</picture>
The srcset attribute allows you to provide the browser with a set of image sources to choose from based on the user's viewport size or display density. Each entry in srcset is called an image candidate string and consists of a URL followed by an optional descriptor — either a width descriptor (like 300w) or a pixel density descriptor (like 2x).
The sizes attribute tells the browser what display size the image will occupy at various viewport widths, using media conditions and length values. The browser uses this size information together with the width descriptors in srcset to select the most appropriate image. This is why the HTML specification requires that when sizes is present, all srcset entries must use width descriptors — without them, the browser cannot perform the size-based selection that sizes is designed to enable.
This error typically appears in three situations:
- A
srcsetentry has no descriptor at all — the URL is listed without any accompanying width or density value. - A pixel density descriptor (
x) is used alongsidesizes— mixingsizeswithxdescriptors is invalid because the two mechanisms are mutually exclusive. - A typo or formatting issue — for example, writing
600pxinstead of600w, or placing a comma incorrectly.
Why this matters
- Standards compliance: The WHATWG HTML Living Standard explicitly states that when
sizesis specified, all image candidates must use width descriptors. - Correct image selection: Without proper width descriptors, browsers cannot accurately determine which image to download. This may lead to unnecessarily large downloads on small screens or blurry images on large screens.
- Performance: Responsive images are a key performance optimization. A malformed
srcsetdefeats the purpose and can result in wasted bandwidth.
How to fix it
- Determine the intrinsic width (in pixels) of each image file listed in
srcset. - Append the width descriptor to each URL in the format
[width]w, where[width]is the image's actual pixel width. - Ensure no entries use
xdescriptors whensizesis present. If you need density descriptors, remove thesizesattribute entirely. - Make sure every entry has a descriptor — bare URLs without any descriptor are invalid when
sizesis used.
Examples
Missing width descriptor
This triggers the validation error because the srcset URL has no width descriptor:
<img
src="/img/photo.jpg"
srcset="/img/photo.jpg"
sizes="(max-width: 600px) 100vw, 600px"
alt="A sunset over the mountains"
>
Fixed by adding the width descriptor:
<img
src="/img/photo.jpg"
srcset="/img/photo.jpg 600w"
sizes="(max-width: 600px) 100vw, 600px"
alt="A sunset over the mountains"
>
Using pixel density descriptors with sizes
This is invalid because x descriptors cannot be combined with the sizes attribute:
<img
src="/img/photo.jpg"
srcset="/img/photo.jpg 1x, /img/photo-2x.jpg 2x"
sizes="(max-width: 800px) 100vw, 800px"
alt="A sunset over the mountains"
>
Fixed by switching to width descriptors:
<img
src="/img/photo.jpg"
srcset="/img/photo.jpg 800w, /img/photo-2x.jpg 1600w"
sizes="(max-width: 800px) 100vw, 800px"
alt="A sunset over the mountains"
>
Alternatively, if you only need density-based selection and don't need sizes, remove it:
<img
src="/img/photo.jpg"
srcset="/img/photo.jpg 1x, /img/photo-2x.jpg 2x"
alt="A sunset over the mountains"
>
Multiple image sources with width descriptors
A complete responsive image setup with several sizes:
<img
src="/img/photo-800.jpg"
srcset=" /img/photo-400.jpg 400w, /img/photo-800.jpg 800w, /img/photo-1200.jpg 1200w "
/img/photo-400.jpg 400w,
/img/photo-800.jpg 800w,
/img/photo-1200.jpg 1200w
sizes="(max-width: 480px) 100vw, (max-width: 960px) 50vw, 800px"
alt="A sunset over the mountains"
>
Each URL is paired with a w descriptor that matches the image's intrinsic pixel width. The sizes attribute then tells the browser how wide the image will display at each breakpoint, allowing it to pick the best candidate.
When a <select> element is marked as required, the browser needs a way to determine whether the user has made a deliberate choice. The HTML specification requires that the first <option> element act as a placeholder — a non-selectable default that represents "no choice made." For the browser's constraint validation to work correctly, this placeholder option must have an empty value attribute (value=""), or it must have no text content at all.
This requirement only applies when all three conditions are met:
- The
<select>has arequiredattribute. - The
<select>does not have amultipleattribute. - The
<select>does not have asizeattribute with a value greater than1.
In this configuration, the <select> renders as a standard single-selection dropdown, and the first <option> with an empty value serves as the "please choose" prompt. If the user submits the form without changing the selection from this placeholder, the browser will block submission and display a validation message — just as it would for an empty required text input.
Why this matters
- Form validation: Without a proper placeholder option, the browser may consider the first option as a valid selection, allowing the form to submit even when the user hasn't actively chosen anything. This defeats the purpose of
required. - Accessibility: Screen readers and assistive technologies rely on standard patterns. A placeholder option clearly communicates to all users that a selection is expected.
- Standards compliance: The WHATWG HTML specification explicitly defines this constraint, and violating it produces a validation error.
How to fix it
- Add a placeholder
<option>as the first child of the<select>, withvalue=""and descriptive prompt text (e.g., "Choose an option"). - Alternatively, if you don't want a visible placeholder, the first
<option>can have no text content at all (<option value=""></option>), though this is less user-friendly. - Another approach is to add a
sizeattribute equal to the number of options, or add themultipleattribute — but these change the visual presentation from a dropdown to a list box, so they're only appropriate if that's the desired UI.
Examples
❌ Incorrect: first option has a non-empty value
<selectrequired>
<optionvalue="s">Small</option>
<optionvalue="m">Medium</option>
<optionvalue="l">Large</option>
</select>
Here, "Small" is preselected and has a non-empty value. The browser treats it as a valid choice, so required validation never triggers — the form can be submitted without the user making an active decision.
❌ Incorrect: placeholder option has a non-empty value
<selectrequired>
<optionvalue="none">Choose a size</option>
<optionvalue="s">Small</option>
<optionvalue="m">Medium</option>
<optionvalue="l">Large</option>
</select>
The first option looks like a placeholder, but its value is "none" rather than empty. The validator flags this because the browser considers "none" a valid value.
✅ Correct: placeholder option with an empty value
<selectrequired>
<optionvalue="">Choose a size</option>
<optionvalue="s">Small</option>
<optionvalue="m">Medium</option>
<optionvalue="l">Large</option>
</select>
The first <option> has value="" and serves as a clear prompt. If the user doesn't select a different option, form validation will prevent submission.
✅ Correct: placeholder option with no text content
<selectrequired>
<optionvalue=""></option>
<optionvalue="s">Small</option>
<optionvalue="m">Medium</option>
<optionvalue="l">Large</option>
</select>
This also satisfies the constraint, though it may appear as a blank entry in the dropdown. It can work in cases where a <label> already makes the expected action clear.
✅ Correct: using a size attribute to avoid the requirement
<selectrequiredsize="3">
<optionvalue="s">Small</option>
<optionvalue="m">Medium</option>
<optionvalue="l">Large</option>
</select>
By adding size="3" (equal to the number of options), the <select> renders as a list box rather than a dropdown. The placeholder requirement no longer applies because no option is implicitly preselected — the user must click to choose. Note that this changes the visual appearance significantly.
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.
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