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.
Unicode allows certain characters — especially accented letters and other composed characters — to be represented in multiple ways. For example, the letter "é" can be a single precomposed character (U+00E9, NFC form) or a base letter "e" (U+0065) followed by a combining acute accent (U+0301, NFD form). While they may look identical on screen, they are different byte sequences. The HTML specification requires that all attribute values use NFC to ensure consistent behavior across browsers, search engines, and assistive technologies.
This matters for several important reasons:
- String matching and comparison: Browsers and scripts may compare attribute values byte-by-byte. An
idvalue in NFD form won't match a CSS selector or fragment identifier targeting the NFC form, causing broken links and broken styles. - Accessibility: Screen readers and other assistive technologies may process NFC and NFD strings differently, potentially mispronouncing text or failing to match ARIA references.
- Interoperability: Different operating systems produce different normalization forms by default (macOS file systems historically use NFD, for example). Copying text from various sources can introduce non-NFC characters without any visual indication.
- Standards compliance: The WHATWG HTML specification and W3C guidance on normalization explicitly recommend NFC for all HTML content.
The issue most commonly appears when attribute values contain accented characters (like in id, class, alt, title, or value attributes) that were copied from a source using NFD normalization, or when files are created on systems that default to NFD.
To fix the problem, you need to convert the affected attribute values to NFC. You can do this by:
- Retyping the characters directly in your editor, which usually produces NFC by default.
- Using a programming tool such as Python's
unicodedata.normalize('NFC', text), JavaScript'stext.normalize('NFC'), or similar utilities in your language of choice. - Using a text editor that supports normalization conversion (some editors have built-in Unicode normalization features or plugins).
- Running a batch conversion on your HTML files before deployment as part of your build process.
Examples
Incorrect: Attribute value uses NFD (decomposed form)
In this example, the id attribute value for "résumé" uses decomposed characters (base letter + combining accent), which triggers the validation error. The decomposition is invisible in source code but present at the byte level.
<!-- The "é" here is stored as "e" + combining acute accent (NFD) -->
<divid="résumé">
<p>My résumé content</p>
</div>
Correct: Attribute value uses NFC (precomposed form)
Here, the id attribute value uses precomposed characters, which is the correct NFC form.
<!-- The "é" here is stored as a single precomposed character (NFC) -->
<divid="résumé">
<p>My résumé content</p>
</div>
While these two examples look identical in source view, they differ at the byte level. You can verify the normalization form using browser developer tools or a hex editor.
Checking and fixing with JavaScript
You can programmatically normalize attribute values:
<script>
// Check if a string is in NFC
consttext="résumé";
constnfcText=text.normalize("NFC");
console.log(text===nfcText);// false if original was NFD
</script>
Checking and fixing with Python
importunicodedata
text="r\u0065\u0301sume\u0301"# NFD form
normalized=unicodedata.normalize('NFC', text)
print(normalized) # Outputs NFC form: "résumé"
If you encounter this validation error, inspect the flagged attribute value carefully and ensure all characters are in their precomposed NFC form. Adding a normalization step to your build pipeline is a reliable way to prevent this issue from recurring.
The charset meta tag specifies an encoding name that is not the preferred form. Use utf-8 (with a hyphen) instead of utf8.
The HTML specification requires that character encoding declarations use the preferred IANA encoding name. For the Unicode UTF-8 encoding, the preferred name is utf-8, not utf8, UTF8, or other variations. While browsers may still recognize non-preferred names, the W3C validator flags them because the WHATWG HTML standard and IANA character set registry both list utf-8 as the canonical form.
This applies to the <meta charset> declaration and, less commonly, to charset parameters in Content-Type headers or <meta http-equiv> tags.
Incorrect example
<metacharset="utf8">
Correct example
<metacharset="utf-8">
The HTML specification defines specific rules about which autocomplete autofill field names can be paired with which input types. The tel-national token (which represents a phone number without the country code) is classified as requiring a text-based input control. Meanwhile, <input type="tel"> is a specialized control that the spec treats differently from a plain text field. When the validator encounters tel-national on a type="tel" input, it flags the mismatch because the autofill field name is not allowed in that context.
This might seem counterintuitive — a national telephone number value on a telephone input feels like a natural fit. However, the distinction exists because type="tel" already implies a complete telephone number, and the spec maps the broader tel autocomplete token to it. The more granular telephone tokens like tel-national, tel-country-code, tel-area-code, tel-local, tel-local-prefix, and tel-local-suffix are designed for type="text" inputs where a phone number is being broken into individual parts across multiple fields.
Getting this right matters for browser autofill behavior. When the autocomplete value and input type are properly paired according to the spec, browsers can more reliably populate the field with the correct portion of the user's stored phone number. An invalid pairing may cause autofill to silently fail or behave unpredictably across different browsers.
How to fix it
You have two options:
- Change the input type to
text— Usetype="text"if you specifically want the national portion of a phone number (without the country code). This is the right choice when you're splitting a phone number across multiple fields. - Change the autocomplete value to
tel— Useautocomplete="tel"if you want a single field for the full phone number. This pairs correctly withtype="tel".
Examples
❌ Invalid: tel-national on type="tel"
<labelfor="phone">Phone number</label>
<inputid="phone"name="phone"type="tel"autocomplete="tel-national">
This triggers the validation error because tel-national is not allowed on a type="tel" input.
✅ Fix option 1: Change input type to text
<labelfor="phone">Phone number (without country code)</label>
<inputid="phone"name="phone"type="text"autocomplete="tel-national">
Using type="text" satisfies the spec's requirement for the tel-national autofill token. This is ideal when collecting just the national portion of a number.
✅ Fix option 2: Change autocomplete to tel
<labelfor="phone">Phone number</label>
<inputid="phone"name="phone"type="tel"autocomplete="tel">
Using autocomplete="tel" is the correct pairing for type="tel" and tells the browser to autofill the complete phone number.
✅ Splitting a phone number across multiple fields
When you need separate fields for different parts of a phone number, use type="text" with the granular autocomplete tokens:
<fieldset>
<legend>Phone number</legend>
<labelfor="country-code">Country code</label>
<inputid="country-code"name="country-code"type="text"autocomplete="tel-country-code">
<labelfor="national">National number</label>
<inputid="national"name="national"type="text"autocomplete="tel-national">
</fieldset>
The xml:lang attribute is a holdover from XHTML, where it was the standard way to declare the language of an element. In HTML5 (the text/html serialization), the lang attribute is the proper way to specify language. The HTML specification allows xml:lang for compatibility purposes, but only if it is accompanied by a lang attribute with an identical value. If xml:lang appears alone, or if its value doesn't match the lang attribute, the document is non-conforming.
This matters for several reasons. Screen readers and other assistive technologies rely on the lang attribute—not xml:lang—to determine pronunciation and language-specific behavior. Search engines also use lang for content indexing and language detection. Having xml:lang without lang means the language declaration may be ignored entirely, degrading both accessibility and SEO.
In modern HTML5 documents, there is rarely a reason to include xml:lang at all. The lang attribute alone covers all use cases. The only scenario where you might need both is if your document must be compatible with both HTML and XHTML parsers (polyglot markup), in which case the two attributes must carry the same value.
How to Fix
You have two options:
- Remove
xml:langand use onlylang(recommended for HTML5 documents). - Add a
langattribute that matches the existingxml:langvalue (for polyglot documents).
If you do keep both attributes, make sure the values are exactly the same—including case and subtags. For example, lang="en-US" must be paired with xml:lang="en-US", not xml:lang="en".
Examples
Incorrect: xml:lang without lang
<htmlxml:lang="en">
<head>
<title>My Page</title>
</head>
<body>
<pxml:lang="fr">Bonjour le monde</p>
</body>
</html>
This triggers the validation error because both the <html> and <p> elements have xml:lang but no lang attribute.
Incorrect: Mismatched values
<htmllang="en"xml:lang="en-US">
<head>
<title>My Page</title>
</head>
<body>
<p>Hello world</p>
</body>
</html>
Even though both attributes are present, the values "en" and "en-US" don't match, which is also invalid.
Correct: Using only lang (recommended)
<!DOCTYPE html>
<htmllang="en">
<head>
<title>My Page</title>
</head>
<body>
<plang="fr">Bonjour le monde</p>
</body>
</html>
This is the cleanest approach for HTML5 documents. The lang attribute is all you need.
Correct: Both attributes with matching values (polyglot)
<!DOCTYPE html>
<htmllang="en"xml:lang="en">
<head>
<title>My Page</title>
</head>
<body>
<plang="fr"xml:lang="fr">Bonjour le monde</p>
</body>
</html>
If you must keep xml:lang, every element that has it must also have lang with the exact same value.
The alt attribute is one of the most important accessibility features in HTML. When a screen reader encounters an <img> element, it reads the alt text aloud so that visually impaired users understand the image's content and purpose. Without it, screen readers may fall back to reading the file name (e.g., "DSC underscore 0042 dot jpeg"), which is meaningless and confusing. Search engines also use alt text to understand and index image content, so including it benefits SEO as well.
The HTML specification requires the alt attribute on all <img> elements, with only narrow exceptions (such as when the image's role is explicitly overridden via certain ARIA attributes, or when the image is inside a <figure> with a <figcaption> that fully describes it—though even then, including alt is strongly recommended).
How to choose the right alt text
The value of the alt attribute depends on the image's purpose:
- Informative images — Describe the content concisely. For example, a photo of a product should describe the product.
- Functional images — Describe the action or destination, not the image itself. For example, a search icon used as a button should have
alt="Search", notalt="Magnifying glass". - Decorative images — Use an empty
altattribute (alt=""). This tells screen readers to skip the image entirely. Do not omit the attribute—use an empty string. - Complex images (charts, diagrams) — Provide a brief summary in
altand a longer description elsewhere on the page or via a link.
Examples
❌ Missing alt attribute
This triggers the W3C validation error:
<imgsrc="photo.jpg">
A screen reader has no useful information to convey, and the validator flags this as an error.
✅ Informative image with descriptive alt
<imgsrc="photo.jpg"alt="Person holding an orange tabby cat in a sunlit garden">
The alt text describes what the image shows, giving screen reader users meaningful context.
✅ Decorative image with empty alt
<imgsrc="decorative-border.png"alt="">
When an image is purely decorative and adds no information, an empty alt attribute tells assistive technology to ignore it. This is valid and preferred over omitting the attribute.
✅ Functional image inside a link
<ahref="/home">
<imgsrc="logo.svg"alt="Acme Corp — Go to homepage">
</a>
Because the image is the only content inside the link, the alt text must describe the link's purpose.
✅ Image inside a <figure> with <figcaption>
<figure>
<imgsrc="chart.png"alt="Bar chart showing quarterly revenue growth from Q1 to Q4 2024">
<figcaption>Quarterly revenue growth for fiscal year 2024.</figcaption>
</figure>
Even when a <figcaption> is present, including a descriptive alt attribute is best practice. The alt should describe the image itself, while the <figcaption> provides additional context visible to all users.
Common mistakes to avoid
- Don't start with "Image of" or "Picture of" — Screen readers already announce the element as an image. Write
alt="Golden retriever playing fetch", notalt="Image of a golden retriever playing fetch". - Don't use the file name —
alt="IMG_4392.jpg"is not helpful. - Don't duplicate surrounding text — If the text next to the image already describes it, use
alt=""to avoid redundancy. - Don't omit
altthinking it's optional — A missingaltattribute andalt=""are fundamentally different. Missing means the screen reader may announce the file path; empty means the screen reader intentionally skips the image.
The autocomplete="new-password" value can only be used on <input> elements whose type accepts password input, specifically type="password".
The autocomplete attribute helps browsers autofill form fields. However, certain autofill tokens are restricted to specific input types. The new-password token tells the browser to suggest a new, generated password — which only makes sense on a password field. If you use it on a type="text", type="email", or other non-password input, the validator will flag it as invalid.
The same restriction applies to current-password. Both tokens are exclusively valid on <input type="password">.
Invalid Example
<labelfor="pass">Create a password</label>
<inputtype="text"id="pass"autocomplete="new-password">
Valid Example
<labelfor="pass">Create a password</label>
<inputtype="password"id="pass"autocomplete="new-password">
If your field is not meant to collect a password, use a different autocomplete value appropriate for the input type, such as username, email, or off.
The HTML specification defines specific rules about which autocomplete values can be used on which form elements. The street-address token is categorized as a "multiline" autofill field because street addresses often span multiple lines (e.g., "123 Main St\nApt 4B"). Since <input> elements only accept single-line text, the spec prohibits using street-address with them. The <textarea> element, on the other hand, naturally supports multiline content, making it the appropriate host for this token.
This matters for several reasons. First, browsers use autocomplete values to offer autofill suggestions. When the element type doesn't match the expected data format, browsers may not autofill correctly or may ignore the hint entirely. Second, standards compliance ensures consistent behavior across different browsers and assistive technologies. Third, using the correct pairing improves the user experience — users expect their full street address to appear in a field that can actually display it properly.
You have two approaches to fix this:
Use a
<textarea>— If you want the full street address in a single field, switch from<input>to<textarea>. This is the most semantically correct choice when you expect multiline address data.Use line-specific tokens on
<input>elements — If your form design uses separate single-line fields for each part of the address, useaddress-line1,address-line2, andaddress-line3instead. These tokens are explicitly allowed on<input>elements.
Examples
❌ Invalid: street-address on an <input>
<labelfor="address">Street Address</label>
<inputtype="text"id="address"name="address"autocomplete="street-address">
This triggers the validation error because street-address requires a multiline control.
✅ Fix: Use a <textarea> with street-address
<labelfor="address">Street Address</label>
<textareaid="address"name="address"autocomplete="street-address"></textarea>
The <textarea> supports multiline text, so street-address is valid here.
✅ Fix: Use line-specific tokens on <input> elements
<labelfor="address1">Address Line 1</label>
<inputtype="text"id="address1"name="address1"autocomplete="address-line1">
<labelfor="address2">Address Line 2</label>
<inputtype="text"id="address2"name="address2"autocomplete="address-line2">
The address-line1, address-line2, and address-line3 tokens are single-line autofill fields and are perfectly valid on <input> elements. This approach is common in forms that break the address into separate fields for apartment numbers, building names, or other details.
Summary of allowed pairings
| Token | <input> | <textarea> |
|---|---|---|
street-address | ❌ Not allowed | ✅ Allowed |
address-line1 | ✅ Allowed | ✅ Allowed |
address-line2 | ✅ Allowed | ✅ Allowed |
address-line3 | ✅ Allowed | ✅ Allowed |
Choose the approach that best fits your form layout. If you prefer a single address field, use <textarea> with street-address. If you prefer structured, separate fields, use <input> elements with the appropriate address-line tokens.
In URLs, percent-encoding is used to represent special or reserved characters. The format is a % sign followed by exactly two hexadecimal digits (0–9, A–F), such as %20 for a space or %3F for a question mark. When the browser encounters a % in a URL, it expects the next two characters to be valid hex digits. If they aren't — for example, % followed by a letter like G, a non-hex character, or nothing at all — the URL is considered malformed.
This issue specifically targets the srcset attribute on <source> elements (commonly used inside <picture> elements), where one or more image candidate URLs contain an invalid percent sequence. The most common causes are:
- A literal
%sign in a query parameter — e.g.,?quality=80%where%is meant literally but isn't encoded. - Truncated percent-encoding — e.g.,
%2instead of%2F, possibly from a copy-paste error or a broken URL-encoding function. - Double-encoding gone wrong — a URL that was partially encoded, leaving some
%characters in an ambiguous state.
This matters because browsers may handle malformed URLs inconsistently. Some browsers might try to recover gracefully, while others may fail to load the image entirely. Invalid URLs also break standards compliance, can cause issues with CDNs and caching layers, and make your markup unreliable across different environments.
How to fix it
- Find the offending
%character in yoursrcsetURL. - If the
%is meant literally (e.g., as part of a percentage value like80%), encode it as%25. - If the
%is part of an incomplete percent-encoding (e.g.,%2instead of%2F), correct it to the full three-character sequence. - Review your URL-generation logic — if URLs are built dynamically by a CMS, template engine, or server-side code, ensure proper encoding is applied before output.
Examples
Invalid: literal % in srcset URL
The % in quality=80% is not followed by two hex digits, so it's treated as a broken percent-encoding sequence.
<picture>
<source
srcset="https://example.com/photo.webp?quality=80%"
type="image/webp">
<imgsrc="https://example.com/photo.jpg"alt="A scenic landscape">
</picture>
Valid: % encoded as %25
Replacing the bare % with %25 produces a valid URL.
<picture>
<source
srcset="https://example.com/photo.webp?quality=80%25"
type="image/webp">
<imgsrc="https://example.com/photo.jpg"alt="A scenic landscape">
</picture>
Invalid: truncated percent-encoding in a file path
Here, %2 is incomplete — it should be %2F (which decodes to /) or some other valid sequence.
<picture>
<source
srcset="https://example.com/images%2photo.webp 1x"
type="image/webp">
<imgsrc="https://example.com/photo.jpg"alt="Product photo">
</picture>
Valid: corrected percent-encoding
The sequence is now %2F, a properly formed encoding.
<picture>
<source
srcset="https://example.com/images%2Fphoto.webp 1x"
type="image/webp">
<imgsrc="https://example.com/photo.jpg"alt="Product photo">
</picture>
Invalid: unencoded % in srcset with multiple candidates
Every URL in the srcset list must be valid. Here, both candidates contain a bare %.
<picture>
<source
srcset="https://example.com/img.webp?w=480&q=75% 480w, https://example.com/img.webp?w=800&q=75% 800w"
https://example.com/img.webp?w=800&q=75% 800w
type="image/webp">
<imgsrc="https://example.com/img.jpg"alt="Blog header image">
</picture>
Valid: all candidates properly encoded
<picture>
<source
srcset="https://example.com/img.webp?w=480&q=75%25 480w, https://example.com/img.webp?w=800&q=75%25 800w"
https://example.com/img.webp?w=800&q=75%25 800w
type="image/webp">
<imgsrc="https://example.com/img.jpg"alt="Blog header image">
</picture>
Note that in the corrected example, & is also encoded as & within the HTML attribute, which is required for valid markup when using ampersands in attribute values.
The lang attribute on the <html> element tells browsers, screen readers, and search engines what language the page content is written in. Its value must follow the BCP 47 standard, which uses ISO 639 language codes as the primary language subtag. When the validator reports that a language subtag "is not a valid ISO language part of a language tag," it means the code you provided doesn't match any recognized language in the ISO 639 registry.
Common Causes
Typos in the language code
A simple misspelling like emg instead of en, or fre instead of fr, will trigger this error.
Using a country code instead of a language code
Country codes (ISO 3166) and language codes (ISO 639) are different standards. For example, uk is the country code for the United Kingdom, but it's also the valid language code for Ukrainian. Using gb (Great Britain) as a language would be invalid. Similarly, us is not a language code — you need en for English or en-US for American English specifically.
Using made-up or deprecated codes
Codes like xx, en-UK, or other non-standard values will fail validation. Note that while en-US and en-GB are valid (language-region format), en-UK is not because UK is not the correct ISO 3166-1 region subtag for the United Kingdom — GB is.
Why This Matters
- Accessibility: Screen readers rely on the
langattribute to select the correct pronunciation rules and voice profile. An invalid language code can cause assistive technology to mispronounce content or fall back to a default language. - SEO: Search engines use the
langattribute as a signal for serving the right content to users in the appropriate language and region. - Browser behavior: Browsers use the language tag for spell-checking, hyphenation, font selection, and other language-sensitive rendering decisions.
How to Fix It
- Identify the language your page is written in.
- Look up the correct ISO 639-1 two-letter code (preferred) or ISO 639-2 three-letter code for that language.
- If you need to specify a regional variant, append a hyphen and the ISO 3166-1 region code (e.g.,
pt-BRfor Brazilian Portuguese). - Replace the invalid value in the
langattribute.
Some commonly used valid language codes:
| Language | Code |
|---|---|
| English | en |
| English (US) | en-US |
| English (UK) | en-GB |
| Spanish | es |
| French | fr |
| German | de |
| Portuguese (Brazil) | pt-BR |
| Chinese (Simplified) | zh-Hans |
| Japanese | ja |
| Arabic | ar |
Examples
❌ Invalid: Typo in language code
<htmllang="emg">
❌ Invalid: Country code used instead of language code
<htmllang="us">
❌ Invalid: Incorrect region subtag
<htmllang="en-UK">
❌ Invalid: Made-up language code
<htmllang="english">
✅ Valid: Correct two-letter language code
<htmllang="en">
✅ Valid: Language with region subtag
<htmllang="en-GB">
✅ Valid: Full document with proper lang attribute
<!DOCTYPE html>
<htmllang="fr">
<head>
<metacharset="utf-8">
<title>Ma page</title>
</head>
<body>
<p>Bonjour le monde !</p>
</body>
</html>
You can verify your language tag using the IANA Language Subtag Registry or the BCP 47 Language Subtag Lookup tool to ensure your code is valid before updating your markup.
The 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>
A sizes value that starts with auto lets the browser work out an image's display size on its own, but the specification only permits it when the same <img> is lazy-loaded with loading="lazy".
The auto keyword removes the need to hand-write media-condition source sizes for a responsive image. For the browser to measure the rendered width and pick a candidate from srcset, the layout around the image has to already exist when the image is fetched. That holds for a lazy-loaded image, which is requested only as it approaches the viewport. An eagerly loaded image can be fetched before its box is laid out, so auto would have nothing to measure, and the validator rejects it.
To fix the error, add loading="lazy" to the same <img>. If the image has to load eagerly, such as a hero image at the top of the page, drop the auto keyword and give sizes explicit lengths instead.
Invalid example
<img
srcset="small.jpg 480w, large.jpg 1024w"
sizes="auto"
src="large.jpg"
alt="Product photo">
Valid example
<img
srcset="small.jpg 480w, large.jpg 1024w"
sizes="auto"
loading="lazy"
src="large.jpg"
alt="Product photo">
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.
When an img element has a sizes attribute, every image candidate in the srcset attribute must include a width descriptor (like 480w), not a pixel density descriptor (like 2x) or a bare URL.
The srcset attribute accepts two types of descriptors, but they cannot be mixed, and the choice depends on whether sizes is present.
Width descriptors (w) tell the browser the actual pixel width of each source image. The browser then uses the sizes attribute to determine how much space the image will occupy in the layout and picks the best candidate from srcset. This pairing of srcset with width descriptors and sizes is how responsive image selection works.
Pixel density descriptors (x) tell the browser which image to use based on the device's pixel density (e.g., 1x for standard screens, 2x for retina). When using density descriptors, the sizes attribute has no role and should be omitted.
The validation error appears when sizes is present but one or more entries in srcset lack a w descriptor. A bare URL with no descriptor is treated as 1x by default, which conflicts with the requirement that sizes demands width descriptors.
Invalid example
<img
src="photo-800.jpg"
srcset="photo-400.jpg, photo-800.jpg 2x"
sizes="(max-width: 600px) 100vw, 50vw"
alt="A landscape photo">
Here, photo-400.jpg has no descriptor (defaults to 1x), and photo-800.jpg uses 2x. Because sizes is present, the validator expects every candidate to specify a width.
Fixed example
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="(max-width: 600px) 100vw, 50vw"
alt="A landscape photo">
Each candidate now includes a width descriptor that reflects the image's intrinsic width in pixels. The browser compares these widths against the resolved sizes value to choose the most appropriate source.
If you do not need sizes and just want to serve different images for different pixel densities, drop the sizes attribute and use density descriptors instead:
<img
src="photo-400.jpg"
srcset="photo-400.jpg 1x, photo-800.jpg 2x"
alt="A landscape photo">
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.
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 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.
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