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.
URLs follow strict syntax rules defined by RFC 3986. Within the path segment of a URL, only a specific set of characters is allowed: unreserved characters (letters, digits, -, ., _, ~), percent-encoded characters (like %20), and certain reserved sub-delimiters. When the W3C validator encounters a character outside this allowed set in a <link> element’s href attribute, it flags the error.
Common causes of this issue include:
- Template placeholders left in the URL, such as {{variable}} or ${path}, where curly braces and dollar signs haven’t been resolved or encoded.
- Spaces in file paths, such as href="styles/my file.css" instead of using %20 or renaming the file.
- Copy-paste errors that introduce invisible or special Unicode characters.
- Backslashes (\) used instead of forward slashes (/), which is a common mistake on Windows systems.
- Unencoded query-like characters placed in the path portion of the URL.
This matters because browsers may interpret malformed URLs inconsistently. A URL that works in one browser might fail in another. Additionally, invalid URLs can break resource loading, cause accessibility issues when assistive technologies try to process the document, and lead to unexpected behavior with proxies, CDNs, or other intermediaries that strictly parse URLs.
To fix the issue, inspect the href value reported in the error and either:
- Remove the illegal character if it was included by mistake.
- Percent-encode the character if it must be part of the URL (e.g., a space becomes %20, a pipe | becomes %7C).
- Rename the referenced file or directory to avoid special characters altogether (the simplest and most reliable approach).
Examples
Incorrect: Space in the path
<link rel="stylesheet" href="styles/my styles.css">
The space character is not allowed in a URL path segment. The validator will flag this as an illegal character.
Fixed: Percent-encode the space
<link rel="stylesheet" href="styles/my%20styles.css">
Better fix: Rename the file to avoid spaces
<link rel="stylesheet" href="styles/my-styles.css">
Incorrect: Template placeholder left unresolved
<link rel="stylesheet" href="styles/{{theme}}/main.css">
Curly braces { and } are not valid in URL path segments. This commonly happens with server-side or client-side templating syntax that wasn’t processed before the HTML was served.
Fixed: Use a valid resolved path
<link rel="stylesheet" href="styles/dark/main.css">
Incorrect: Backslash used as path separator
<link rel="stylesheet" href="styles\main.css">
Backslashes are not valid URL characters. URLs always use forward slashes.
Fixed: Use forward slashes
<link rel="stylesheet" href="styles/main.css">
Incorrect: Pipe character in the URL
<link rel="stylesheet" href="styles/font|icon.css">
Fixed: Percent-encode the pipe character
<link rel="stylesheet" href="styles/font%7Cicon.css">
Full valid document example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Webpage</title>
<link rel="stylesheet" href="styles/main.css">
<link rel="icon" href="images/favicon.ico">
</head>
<body>
<h1>Welcome to my webpage!</h1>
<p>Here is some content.</p>
</body>
</html>
When in doubt, run your URL through a URL encoder or validator separately to confirm all characters are legal. As a general best practice, stick to lowercase letters, digits, hyphens, and forward slashes in your file and directory names—this avoids encoding issues entirely and makes your URLs clean and predictable.
The autocomplete attribute helps browsers autofill form fields with previously saved user data. The HTML specification defines a strict set of valid values, and each one maps to a specific type of information (like a name, email address, phone number, or street address). The string "contact" by itself is not a valid autofill field name — it’s a contact type token, which is a modifier meant to be combined with a field name to distinguish between different types of contact information.
The HTML spec defines two contact type tokens: "home", "work", "mobile", "fax", and "pager" (for phone-related fields), as well as the broader "shipping" and "billing" scoping tokens. The token "contact" doesn’t exist as a standalone value at all. You may have confused it with a contact type prefix pattern like "home email" or "work tel", or you may have intended to use a specific field name entirely.
Getting the autocomplete value right matters for several reasons. Browsers rely on these exact tokens to offer relevant autofill suggestions. Screen readers and assistive technologies may also use this information to help users understand what data a field expects. An invalid value means the browser will likely ignore the attribute entirely, degrading the user experience — especially on mobile devices where autofill is heavily used.
To fix the issue, determine what kind of information the input field is collecting and use the appropriate autofill field name. Common valid values include "name", "email", "tel", "street-address", "postal-code", "organization", and "username". If you want to indicate that this is specifically a contact email or phone (as opposed to, say, a billing one), you don’t use "contact" — instead, you can omit the modifier entirely or use a section-scoping approach.
Examples
❌ Invalid: Using “contact” as the autocomplete value
<label for="email">Contact Email</label>
<input type="email" id="email" name="email" autocomplete="contact">
The value "contact" is not a recognized autofill field name, so the browser cannot determine what to autofill.
✅ Fixed: Using a valid autofill field name
<label for="email">Contact Email</label>
<input type="email" id="email" name="email" autocomplete="email">
The value "email" is a valid autofill field name that tells the browser to suggest saved email addresses.
✅ Fixed: Using a valid combination with a section or contact type token
If you need to differentiate between types of phone numbers, you can use tokens like "home", "work", or "mobile" as prefixes:
<label for="work-tel">Work Phone</label>
<input type="tel" id="work-tel" name="work-tel" autocomplete="work tel">
<label for="home-email">Personal Email</label>
<input type="email" id="home-email" name="home-email" autocomplete="home email">
Common valid autocomplete values
Here are some frequently used valid autofill field names:
| Value | Purpose |
|---|---|
| "name" | Full name |
| "email" | Email address |
| "tel" | Phone number |
| "username" | Username |
| "new-password" | New password (for registration) |
| "current-password" | Existing password (for login) |
| "street-address" | Street address |
| "postal-code" | ZIP or postal code |
| "country-name" | Country name |
| "organization" | Company or organization |
| "off" | Disable autofill |
For the complete list of valid values and their permitted combinations, refer to the WHATWG autofill specification.
A URL is made up of several parts: scheme, host (domain), path, query, and fragment. While some of these parts allow certain special characters (often percent-encoded), the host portion has strict rules. Domain names follow the DNS naming conventions, which only permit ASCII letters (a-z, A-Z), digits (0-9), hyphens (-), and dots (.) as label separators. Spaces are categorically forbidden.
This validation error typically occurs in two scenarios:
- A literal space appears in the domain, e.g., http://my domain.com. This is often a typo or a copy-paste error.
- A percent-encoded space (%20) appears in the domain, e.g., http://my%20domain.com. While %20 is valid in URL paths and query strings, it is not valid in the host portion. Percent-encoding does not make a space legal in a domain name — it still resolves to a space character, which DNS cannot handle.
Why this is a problem
- Broken links: Browsers cannot resolve a domain with spaces to an actual server. Users clicking the link will get an error or be taken nowhere.
- Accessibility: Screen readers and assistive technologies may announce the link, but users will encounter a dead end, creating a frustrating experience.
- Standards compliance: The WHATWG URL Standard explicitly forbids spaces in the host component. The W3C validator flags this to help you catch what is almost certainly a mistake.
- SEO impact: Search engine crawlers will treat the URL as invalid and will not follow or index it.
How to fix it
- Check for typos: The most common fix is to correct the domain to the actual, valid domain name you intended.
- Replace spaces with hyphens: If the intended domain genuinely has a word separator, the standard convention is to use hyphens (e.g., my-domain.com).
- Remove spaces entirely: Sometimes spaces are accidentally introduced and simply need to be removed (e.g., mydomain.com).
- Check the path vs. host: If the space belongs in a file path or query parameter rather than the domain, make sure it’s in the correct part of the URL and properly percent-encoded there.
Examples
❌ Literal space in the domain
<a href="http://my domain.com/page">Visit site</a>
❌ Percent-encoded space in the domain
<a href="http://my%20domain.com/page">Visit site</a>
✅ Fixed: use a hyphen in the domain
<a href="http://my-domain.com/page">Visit site</a>
✅ Fixed: remove the space entirely
<a href="http://mydomain.com/page">Visit site</a>
✅ Spaces are fine in the path (percent-encoded)
Note that %20 is valid in the path portion of a URL — just not in the domain:
<a href="http://mydomain.com/my%20page">Visit page</a>
Common mistake: space before or after the domain
Sometimes the space is hard to spot because it’s at the beginning or end of the URL, or between the scheme and domain:
<!-- ❌ Trailing space in domain -->
<a href="http://mydomain.com /page">Visit site</a>
<!-- ✅ Fixed -->
<a href="http://mydomain.com/page">Visit site</a>
If your URLs are generated dynamically (e.g., from a CMS or database), make sure to trim whitespace from the domain portion before constructing the full URL. A quick way to catch these issues during development is to validate your HTML regularly with the W3C Markup Validation Service.
The HTML specification requires that the width and height attributes on <img> elements, when present, contain a string representing a non-negative integer — that is, a sequence of one or more ASCII digits like "0", "150", or "1920". An empty string ("") does not satisfy this requirement, so the W3C validator flags it as an error.
This issue commonly arises when:
- A CMS or templating engine outputs width="" or height="" because no dimension value was configured.
- JavaScript dynamically sets img.setAttribute("width", "") instead of removing the attribute.
- A developer adds the attributes as placeholders intending to fill them in later but forgets to do so.
Why it matters
Providing valid width and height attributes is one of the most effective ways to prevent Cumulative Layout Shift (CLS). Browsers use these values to calculate the image’s aspect ratio and reserve the correct amount of space before the image loads. When the values are empty strings, the browser cannot determine the aspect ratio, so no space is reserved — leading to layout shifts as images load in, which hurts both user experience and Core Web Vitals scores.
Beyond performance, invalid attribute values can cause unpredictable rendering behavior across browsers. Some browsers may ignore the attribute, others may interpret the empty string as 0, collapsing the image to zero pixels in that dimension. Standards-compliant HTML also improves accessibility by ensuring assistive technologies can parse the document reliably.
Examples
❌ Invalid: empty string values
<img src="photo.jpg" alt="A sunset over the ocean" width="" height="">
Both width and height are set to empty strings, which is not valid.
✅ Fixed: provide actual dimensions
<img src="photo.jpg" alt="A sunset over the ocean" width="800" height="600">
Replace the empty strings with the image’s actual pixel dimensions. These values should reflect the image’s intrinsic (natural) size. CSS can still be used to scale the image visually — the browser will use the width and height ratio to reserve the correct space.
✅ Fixed: remove the attributes entirely
<img src="photo.jpg" alt="A sunset over the ocean">
If you don’t know the dimensions or prefer to handle sizing purely through CSS, remove the attributes altogether. An absent attribute is valid; an empty one is not.
❌ Invalid: only one attribute is empty
<img src="banner.jpg" alt="Promotional banner" width="1200" height="">
Even if only one attribute has an empty value, the validation error will be triggered for that attribute.
✅ Fixed: both attributes with valid values
<img src="banner.jpg" alt="Promotional banner" width="1200" height="400">
Fixing dynamic/template-generated markup
If a template language is outputting empty attributes, use a conditional to omit them when no value is available. For example, in a template:
<!-- Instead of always outputting the attributes: -->
<img src="photo.jpg" alt="Description" width="" height="">
<!-- Conditionally include them only when values exist: -->
<img src="photo.jpg" alt="Description" width="800" height="600">
If you’re setting dimensions via JavaScript, remove the attribute rather than setting it to an empty string:
// ❌ Don't do this
img.setAttribute("width", "");
// ✅ Do this instead
img.removeAttribute("width");
// ✅ Or set a valid value
img.setAttribute("width", "800");
A note on values
The width and height attributes only accept non-negative integers — whole numbers without units, decimals, or percentage signs. Values like "100px", "50%", or "3.5" are also invalid. Use plain integers like "100" or "600". If you need responsive sizing with percentages or other CSS units, apply those through CSS styles instead.
The inputmode attribute is a global attribute that can be applied to any element that is editable, including <input> elements and elements with contenteditable. It tells the browser which type of virtual keyboard to present—for example, a numeric keypad, a telephone dialpad, or a URL-optimized keyboard. This is particularly useful on mobile devices where the on-screen keyboard can be tailored to the expected input.
The W3C validator raises this as an informational warning, not an error. The inputmode attribute is part of the WHATWG HTML Living Standard and is valid HTML. However, the validator flags it because browser support, while now quite broad, has historically been inconsistent. Older versions of Safari, Firefox, and some less common browsers lacked support for certain inputmode values. When inputmode is not recognized, the browser simply ignores it and shows the default keyboard—so it degrades gracefully and won’t break your page.
The valid values for inputmode are:
- none — No virtual keyboard; useful when the page provides its own input interface.
- text — Standard text keyboard (the default).
- decimal — Numeric keyboard with a decimal separator, ideal for fractional numbers.
- numeric — Numeric keyboard without a decimal separator, ideal for PINs or zip codes.
- tel — Telephone keypad layout with digits 0–9, *, and #.
- search — A keyboard optimized for search input, which may include a “Search” or “Go” button.
- email — A keyboard optimized for email entry, typically including @ and . prominently.
- url — A keyboard optimized for URL entry, typically including / and .com.
It’s important to understand the difference between inputmode and the type attribute. The type attribute on <input> defines the semantics and validation behavior of the field (e.g., type="email" validates that the value looks like an email address). The inputmode attribute only affects the virtual keyboard hint and has no impact on validation or semantics. This makes inputmode especially useful when you need a specific keyboard but the field type doesn’t match—for example, a numeric PIN field that should remain type="text" to avoid the spinner controls that come with type="number".
How to fix it
Since this is a warning rather than an error, no fix is strictly required. However, you should:
- Test on your target browsers and devices to confirm the virtual keyboard behaves as expected.
- Pair inputmode with the appropriate type and pattern attributes to ensure proper validation and semantics, since inputmode alone does not enforce any input constraints.
- Accept graceful degradation — in browsers that don’t support inputmode, users will simply see the default keyboard, which is still functional.
There is no widely adopted polyfill for inputmode because it controls a browser-native UI feature (the virtual keyboard) that JavaScript cannot directly replicate. The best strategy is to treat it as a progressive enhancement.
Examples
Using inputmode for a numeric PIN field
This example triggers the validator warning. The code is valid, but the validator advises caution:
<label for="pin">Enter your PIN:</label>
<input id="pin" type="text" inputmode="numeric" pattern="[0-9]*">
Here, type="text" keeps the field free of number-spinner controls, inputmode="numeric" requests a numeric keypad on mobile, and pattern="[0-9]*" provides client-side validation. This combination is the recommended approach for PIN or verification code fields.
Using inputmode for a currency amount
<label for="amount">Amount ($):</label>
<input id="amount" type="text" inputmode="decimal" pattern="[0-9]*\.?[0-9]{0,2}">
The decimal value displays a numeric keyboard that includes a decimal point, which is ideal for monetary values.
Falling back to type when inputmode is unnecessary
If the semantic input type already provides the correct keyboard, you don’t need inputmode at all:
<label for="email">Email address:</label>
<input id="email" type="email">
<label for="phone">Phone number:</label>
<input id="phone" type="tel">
<label for="website">Website:</label>
<input id="website" type="url">
Using the appropriate type gives you both the optimized keyboard and built-in browser validation, making inputmode redundant in these cases.
Using inputmode on a contenteditable element
The inputmode attribute also works on non-input elements that accept user input:
<div contenteditable="true" inputmode="numeric">
Enter a number here
</div>
This is one scenario where inputmode is especially valuable, since contenteditable elements don’t have a type attribute to influence the keyboard.
The autocomplete attribute tells browsers whether and how to autofill a form field. The HTML specification defines a strict set of valid values for this attribute, known as autofill field names. These include values like "on", "off", "name", "email", "username", "new-password", "current-password", "address-line1", "postal-code", "cc-number", and many others. When you use a value that doesn’t appear in this list — such as "nothanks", "nope", or "false" — the W3C validator reports it as an invalid autofill field name.
A common reason developers use made-up values is frustration with browsers ignoring autocomplete="off". Some browsers (notably Chrome) may still autofill certain fields even when autocomplete="off" is set, particularly for login-related fields. This has led to workarounds using random strings, but these are non-standard and can produce unpredictable behavior across different browsers and assistive technologies.
Why This Matters
- Standards compliance: Invalid attribute values make your HTML non-conforming, which can lead to unexpected browser behavior now or in the future.
- Accessibility: Screen readers and other assistive technologies may use the autocomplete attribute to help users fill in forms. A recognized value like "name" or "email" gives these tools meaningful context, while a random string provides none.
- Browser behavior: Browsers are designed to interpret the standard values. An unrecognized value may be treated inconsistently — some browsers might ignore it, others might treat it as "on", and behavior could change between versions.
How to Fix It
If you want to disable autocomplete, use "off":
<input type="text" name="search" autocomplete="off">
If you want to help browsers autofill correctly, use the appropriate autofill field name from the specification:
<input type="email" name="email" autocomplete="email">
If autocomplete="off" isn’t being respected by the browser (a known issue with some login fields in Chrome), consider these standards-compliant alternatives:
- Use autocomplete="new-password" on password fields where you don’t want saved passwords suggested.
- Use a more specific valid token that doesn’t match what the browser is trying to autofill.
- Use the readonly attribute and remove it on focus via JavaScript as a supplementary measure.
Examples
❌ Invalid: arbitrary string as autocomplete value
<form>
<label for="firstName">First name</label>
<input type="text" name="firstName" id="firstName" autocomplete="nothanks">
<label for="userEmail">Email</label>
<input type="email" name="userEmail" id="userEmail" autocomplete="nope">
</form>
Both "nothanks" and "nope" are not valid autofill field names and will trigger the validation error.
✅ Valid: using "off" to disable autocomplete
<form>
<label for="firstName">First name</label>
<input type="text" name="firstName" id="firstName" autocomplete="off">
<label for="userEmail">Email</label>
<input type="email" name="userEmail" id="userEmail" autocomplete="off">
</form>
✅ Valid: using proper autofill field names
<form>
<label for="firstName">First name</label>
<input type="text" name="firstName" id="firstName" autocomplete="given-name">
<label for="userEmail">Email</label>
<input type="email" name="userEmail" id="userEmail" autocomplete="email">
<label for="newPass">New password</label>
<input type="password" name="newPass" id="newPass" autocomplete="new-password">
</form>
Using descriptive autofill tokens like "given-name", "email", and "new-password" is the best approach when you want browsers and assistive technologies to understand your form fields. For a complete list of valid autofill field names, refer to the WHATWG HTML specification’s autofill section.
The lang attribute on the <html> element declares the primary language of the document’s content. When this attribute is missing or set incorrectly, the validator analyzes the text content and attempts to detect the language automatically. If it identifies a likely language, it produces this warning suggesting you add the appropriate lang value.
This matters for several important reasons:
- Accessibility: Screen readers rely on the lang attribute to select the correct pronunciation rules and voice profile. Without it, a screen reader might attempt to read Spanish text using English phonetics, producing unintelligible speech for the user.
- Browser behavior: Browsers use the language declaration for hyphenation, quotation mark styling, spell-checking, and font selection. For example, the CSS hyphens: auto property depends on the lang attribute to apply language-appropriate hyphenation rules.
- Search engines: Search engines use the lang attribute as a signal to understand the language of your content, which helps serve it to the right audience in search results.
- Translation tools: Automatic translation services use the declared language to determine whether (and from which language) to offer translation.
The value of the lang attribute must be a valid BCP 47 language tag. Common examples include en (English), es (Spanish), fr (French), de (German), zh (Chinese), ja (Japanese), and ar (Arabic). You can also specify regional variants like en-US, en-GB, pt-BR, or es-MX.
Examples
Missing lang attribute
This triggers the warning because the validator detects the content language but finds no lang declaration:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Mi Sitio Web</title>
</head>
<body>
<h1>Bienvenido a mi sitio web</h1>
<p>Este es un párrafo en español.</p>
</body>
</html>
Fixed with the correct lang attribute
Adding lang="es" tells browsers, screen readers, and search engines that this document is in Spanish:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Mi Sitio Web</title>
</head>
<body>
<h1>Bienvenido a mi sitio web</h1>
<p>Este es un párrafo en español.</p>
</body>
</html>
Mismatched lang attribute
This can also trigger the warning when the lang value doesn’t match the actual content. Here, the content is in French but the language is declared as English:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Mon Site Web</title>
</head>
<body>
<h1>Bienvenue sur mon site web</h1>
<p>Ceci est un paragraphe en français.</p>
</body>
</html>
The fix is to correct the lang value to match the content:
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Mon Site Web</title>
</head>
<body>
<h1>Bienvenue sur mon site web</h1>
<p>Ceci est un paragraphe en français.</p>
</body>
</html>
Using a regional variant
If you need to specify a regional variation, append the region subtag. For example, es-MX for Mexican Spanish or pt-BR for Brazilian Portuguese:
<!DOCTYPE html>
<html lang="es-MX">
<head>
<meta charset="UTF-8">
<title>Mi Sitio Web</title>
</head>
<body>
<h1>Bienvenido a mi sitio web</h1>
<p>Este es un párrafo en español de México.</p>
</body>
</html>
Sections in a different language
When your document is primarily in one language but contains sections in another, set the main language on <html> and use lang on individual elements for the exceptions:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Website</title>
</head>
<body>
<h1>Welcome to my website</h1>
<p>This site is available in multiple languages.</p>
<blockquote lang="es">
<p>Bienvenido a mi sitio web.</p>
</blockquote>
</body>
</html>
This approach ensures that assistive technologies switch pronunciation rules appropriately when they encounter the foreign-language section, while browsers and search engines still understand the primary document language.
A data: URI embeds resource content directly in a URL rather than pointing to an external file. It follows the format data:[<mediatype>][;base64],<data>. RFC 2397, which governs this scheme, explicitly states that fragment identifiers (the # character followed by a fragment name) are not valid in data: URIs.
This issue most commonly arises when developers try to reference a specific element inside an embedded SVG — for example, a particular <symbol> or element with an id — by appending a fragment like #icon-name to a data: URI. While fragments work in standard URLs (e.g., icons.svg#home), the data: URI scheme simply doesn’t support them.
Why It Matters
- Standards compliance: Browsers may handle invalid data: URIs inconsistently. Some may silently ignore the fragment, while others may fail to render the image entirely.
- Portability: Code that relies on non-standard behavior in one browser may break in another or in a future update.
- Accessibility and tooling: Validators, linters, and assistive technologies expect well-formed URIs. An invalid URI can cause unexpected issues down the chain.
How to Fix It
You have several options depending on your use case:
- Remove the fragment from the data: URI. If the embedded content is a complete, self-contained image, it doesn’t need a fragment reference.
- Inline the SVG directly into the HTML document. This lets you reference internal id values with standard fragment syntax using <use> elements.
- Use an external file instead of a data: URI. Standard URLs like icons.svg#home fully support fragment identifiers.
- Encode the full, standalone SVG into the data: URI so that it contains only the content you need, eliminating the need for a fragment reference.
Examples
❌ Incorrect: Fragment in a data: URI
<img
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Csymbol id='icon'%3E%3Ccircle cx='10' cy='10' r='10'/%3E%3C/symbol%3E%3C/svg%3E#icon"
alt="Icon">
The #icon fragment at the end of the data: URI violates RFC 2397 and triggers the validation error.
✅ Correct: Self-contained SVG in a data: URI (no fragment)
Embed only the content you need directly, without wrapping it in a <symbol> or referencing a fragment:
<img
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Ccircle cx='10' cy='10' r='10'/%3E%3C/svg%3E"
alt="Icon">
✅ Correct: External SVG file with a fragment
Move the SVG to a separate file and reference the specific symbol using a standard URL fragment:
<img src="icons.svg#icon" alt="Icon">
✅ Correct: Inline SVG with <use> referencing an internal id
If you need to reference individual symbols from a sprite, inline the SVG and use fragment references within the same document:
<svg xmlns="http://www.w3.org/2000/svg" hidden>
<symbol id="icon-home" viewBox="0 0 20 20">
<path d="M10 2 L2 10 L4 10 L4 18 L16 18 L16 10 L18 10 Z"/>
</symbol>
</svg>
<svg width="20" height="20" aria-hidden="true">
<use href="#icon-home"></use>
</svg>
This approach gives you full control over individual icons without needing data: URIs at all, and it’s the most flexible option for icon systems.
In the structure of a URL, the @ symbol has a special meaning: it separates the userinfo component (username and password) from the host. A URL with credentials follows this pattern:
scheme://username:password@hostname/path
When the username or password itself contains an @ character — for example, an email address used as a username — the browser or URL parser may not be able to determine where the credentials end and the hostname begins. For instance, in http://user@name:pass@example.com, it’s unclear whether the host is name or example.com.
The URL Standard (maintained by WHATWG) requires that any @ appearing within the userinfo component be percent-encoded as %40. Percent-encoding replaces the literal character with a % followed by its hexadecimal ASCII code (40 for @). This removes the ambiguity and ensures all parsers interpret the URL identically.
While modern browsers may attempt to handle ambiguous URLs, the behavior is not guaranteed to be consistent across all user agents, link checkers, or HTTP clients. Properly encoding these characters ensures reliable behavior everywhere and keeps your HTML valid.
Note: Including credentials directly in URLs is generally discouraged for security reasons, as they may be exposed in browser history, server logs, and referrer headers. Consider alternative authentication methods when possible.
Examples
❌ Incorrect: unencoded @ in the username
<a href="http://user@name:password@example.com/path">Login</a>
Here, the parser cannot reliably distinguish user@name as the username from the @ that separates credentials from the host.
✅ Correct: percent-encoded @ in the username
<a href="http://user%40name:password@example.com/path">Login</a>
The @ within the username is encoded as %40, leaving only one literal @ to serve as the delimiter before the hostname.
❌ Incorrect: unencoded @ in the password
<a href="http://admin:p@ss@example.com/dashboard">Dashboard</a>
✅ Correct: percent-encoded @ in the password
<a href="http://admin:p%40ss@example.com/dashboard">Dashboard</a>
❌ Incorrect: email address used as username without encoding
<a href="ftp://joe@example.org:secret@ftp.example.com/files">Files</a>
✅ Correct: email address with @ percent-encoded
<a href="ftp://joe%40example.org:secret@ftp.example.com/files">Files</a>
To fix this issue, identify every @ character that appears before the final @ in the authority section of the URL and replace it with %40. The last @ in the authority is the actual delimiter and must remain as a literal character.
The autocomplete attribute tells the browser how to handle autofilling a form field. The HTML specification defines a strict set of allowed values: the keywords on and off, and a collection of autofill field names such as name, email, username, new-password, street-address, and many others. The value "none" is not part of this specification, even though it might seem like a logical choice for “no autocomplete.”
This confusion likely arises because some non-web APIs and frameworks use "none" as a keyword to disable features. In HTML, however, the correct keyword to disable autocompletion is "off". Using an invalid value like "none" leads to undefined browser behavior — some browsers may ignore it entirely and autofill anyway, while others might treat it as equivalent to "on". This inconsistency can cause unexpected user experiences and potential security concerns, especially for sensitive fields like passwords or credit card numbers.
Beyond standards compliance, using valid autocomplete values improves accessibility. Assistive technologies and password managers rely on recognized autofill field names to help users fill out forms efficiently. When a valid, descriptive value like "username" or "email" is provided, browsers and assistive tools can offer more accurate suggestions.
How to fix it
Replace "none" with the appropriate valid value:
- Use "off" if you want to disable autofill for the field.
- Use "on" if you want the browser to decide how to autofill the field.
- Use a specific autofill field name if you want to hint at the type of data expected.
Common autofill field names include: name, given-name, family-name, email, username, new-password, current-password, tel, street-address, postal-code, country, cc-number, cc-exp, and cc-name. You can also combine tokens, such as "shipping postal-code" or "billing cc-number", to provide additional context through section and hint tokens.
Note: Even with autocomplete="off", some browsers may still autofill certain fields (particularly login credentials) for security or usability reasons. This is browser-specific behavior and not something the HTML specification can override.
Examples
Incorrect: using "none" to disable autofill
<form>
<label for="user">Username</label>
<input type="text" id="user" name="username" autocomplete="none">
</form>
Correct: using "off" to disable autofill
<form>
<label for="user">Username</label>
<input type="text" id="user" name="username" autocomplete="off">
</form>
Correct: using a specific autofill field name
When you know what kind of data a field collects, providing a descriptive autofill field name is often better than using "on" or "off". This helps browsers offer accurate suggestions:
<form>
<label for="user">Username</label>
<input type="text" id="user" name="username" autocomplete="username">
<label for="email">Email</label>
<input type="email" id="email" name="email" autocomplete="email">
<label for="pwd">New Password</label>
<input type="password" id="pwd" name="password" autocomplete="new-password">
</form>
Correct: using section and hint tokens
You can prefix an autofill field name with a section name or shipping/billing hint to distinguish between multiple addresses in the same form:
<form>
<label for="ship-zip">Shipping postal code</label>
<input type="text" id="ship-zip" name="ship_zip" autocomplete="shipping postal-code">
<label for="bill-zip">Billing postal code</label>
<input type="text" id="bill-zip" name="bill_zip" autocomplete="billing postal-code">
</form>
The autocomplete attribute tells the browser how to handle autofill for a form field. The HTML specification defines a strict set of valid values, which include "on", "off", and a list of autofill field names such as "username", "new-password", "cc-number", "postal-code", and many others. When you use a value that isn’t in this list — such as "nope", "false", "none", or any other made-up string — the W3C validator reports it as an invalid autofill field name.
A common reason developers use values like "nope" is as a workaround because some browsers historically ignored autocomplete="off". In older versions of Chrome and Firefox, the browser would still show autofill suggestions even when off was set, so developers discovered that using an unrecognized value like "nope" effectively tricked the browser into not showing suggestions. While this hack may have worked in practice, it produces invalid HTML and is not a reliable long-term solution since browser behavior around unrecognized values can change at any time.
Why this matters
- Standards compliance: Invalid attribute values make your HTML non-conforming, which can cause issues with tooling, testing pipelines, and accessibility auditors.
- Accessibility: Screen readers and assistive technologies rely on valid autocomplete values to help users fill in forms. Using a correct autofill field name like "given-name" or "email" can significantly improve the experience for users with disabilities. In fact, WCAG 2.1 Success Criterion 1.3.5 specifically recommends using valid autocomplete values for fields that collect user information.
- Browser behavior: Modern browsers have improved their handling of autocomplete="off". Using the standard value is now more reliable than it once was, and using it correctly ensures predictable behavior across browsers.
How to fix it
- To disable autocomplete, replace the invalid value with "off".
- To enable smart autofill, use the appropriate autofill field name from the HTML specification’s list of autofill field names. This is the preferred approach for most user-facing forms.
- For new passwords (e.g., registration or password-change forms), use "new-password" — this tells the browser to suggest a generated password rather than filling in a saved one.
Examples
Invalid: made-up autocomplete value
<input type="text" name="firstName" autocomplete="nope">
Other common invalid values that trigger the same error include "false", "none", "disable", and "no".
Fixed: disabling autocomplete with "off"
<input type="text" name="firstName" autocomplete="off">
Fixed: using a valid autofill field name
Using a specific autofill field name is often better than "off" because it helps browsers and assistive technologies understand the purpose of the field:
<input type="text" name="firstName" autocomplete="given-name">
Fixed: common valid autocomplete values in a form
<form method="post" action="/register">
<label for="name">Full Name</label>
<input type="text" id="name" name="name" autocomplete="name">
<label for="email">Email</label>
<input type="email" id="email" name="email" autocomplete="email">
<label for="newpass">Password</label>
<input type="password" id="newpass" name="password" autocomplete="new-password">
<label for="tel">Phone</label>
<input type="tel" id="tel" name="phone" autocomplete="tel">
<button type="submit">Register</button>
</form>
Some of the most commonly used valid values include: "name", "given-name", "family-name", "email", "username", "new-password", "current-password", "street-address", "postal-code", "country", "tel", "cc-number", and "organization". Refer to the full list in the HTML specification for all available options.
URLs must conform to the URL Living Standard, which forbids tab characters (U+0009) within the host/domain portion of a URL. While some browsers may silently strip tabs and still navigate to the intended destination, this behavior is not guaranteed and should not be relied upon.
This issue typically arises from one of the following scenarios:
- Copy-paste errors: Copying a URL from a document, email, or spreadsheet that inadvertently includes tab characters.
- Template or build tool formatting: A templating engine or code generator inserting whitespace (including tabs) into a URL string, especially when the URL is constructed across multiple lines.
- Manual typos: Accidentally pressing the Tab key while editing an href value, particularly in editors that don’t visualize whitespace.
Tab characters are invisible in most code editors by default, which makes this error frustrating to track down. Enabling “show whitespace” or “show invisible characters” in your editor can help you spot the offending character.
Why this matters
- Standards compliance: The HTML specification requires that href values contain valid URLs. A tab in the domain makes the URL syntactically invalid.
- Accessibility: Screen readers and assistive technologies parse href values to announce link destinations. An invalid URL can lead to confusing or broken announcements.
- Reliability: While major browsers tend to be forgiving and strip tabs before resolving URLs, some HTTP clients, crawlers, or older browsers may not. This can cause broken links in unexpected contexts like RSS readers, email clients, or web scrapers.
How to fix it
- Enable visible whitespace in your editor to locate tab characters.
- Search for tab characters in your href values. In many editors, you can use a regex search for \t within attribute values.
- Remove the tab characters so the URL is a clean, continuous string with no embedded whitespace.
- If URLs are dynamically generated, inspect the code that builds them to ensure no tabs or other whitespace are concatenated into the domain.
Examples
❌ Incorrect: Tab character in the domain
In the example below, a tab character is embedded within the domain name (represented here as 	 for visibility, though in source code it would be an actual invisible tab):
<!-- The tab between "example" and ".com" causes the error -->
<a href="https://example .com/page">Visit Example</a>
Note: The tab character between example and .com is invisible in most editors but triggers the validation error.
✅ Correct: Clean URL with no whitespace
<a href="https://example.com/page">Visit Example</a>
❌ Incorrect: Tab introduced by multi-line URL construction in a template
This can happen when a URL is broken across lines in a template and tabs are used for indentation:
<a href="https://
example.com/page">Visit Example</a>
✅ Correct: URL on a single line with no embedded whitespace
<a href="https://example.com/page">Visit Example</a>
Tip: Finding hidden tabs
If you’re having trouble locating the tab character, try pasting your href value into a tool that reveals character codes, or run a quick check in your browser’s developer console:
// Check for tabs in a URL string
const url = document.querySelector('a').getAttribute('href');
console.log(url.includes('\t')); // true if a tab is present
console.log(JSON.stringify(url)); // shows \t explicitly in the output
The srcset attribute on img and source elements accepts a comma-separated list of image candidate strings. Each candidate consists of a URL optionally followed by a width descriptor (e.g., 300w) or a pixel density descriptor (e.g., 2x). The URL in each candidate must conform to the URL Standard, which does not permit raw square brackets in the query string of an HTTP or HTTPS URL.
This issue commonly arises when a backend framework or CMS generates URLs that use square brackets in query parameters — for example, ?filter[size]=large or ?dimensions[]=300. While many browsers are lenient and will load these URLs anyway, they are technically invalid according to the URL specification. Using invalid URLs can lead to unpredictable behavior across different browsers, HTML parsers, and tools that process your markup. It also means your HTML fails W3C validation, which can mask other, more critical issues in your code.
You have two main approaches to fix this:
-
Percent-encode the brackets. Replace every [ with %5B and every ] with %5D. This preserves the intended query parameter structure while making the URL spec-compliant. Your server should interpret percent-encoded brackets identically to raw brackets.
-
Eliminate brackets from the URL. If you control the server-side code, consider using alternative query parameter conventions that don’t rely on brackets — for instance, using dot notation (filter.size=large), comma-separated values (dimensions=300,400), or repeated parameter names (dimension=300&dimension=400).
When fixing these URLs, also make sure each image candidate follows the correct format: a valid URL, followed by optional whitespace and a descriptor, with candidates separated by commas.
Examples
Incorrect — raw square brackets in query string
This triggers the validation error because [ and ] appear unescaped in the srcset URLs:
<img
src="photo.jpg"
srcset="photo.jpg?crop[width]=400 400w, photo.jpg?crop[width]=800 800w"
sizes="(max-width: 600px) 400px, 800px"
alt="A landscape photo">
Fixed — percent-encoded brackets
Replacing [ with %5B and ] with %5D makes the URLs valid:
<img
src="photo.jpg"
srcset="photo.jpg?crop%5Bwidth%5D=400 400w, photo.jpg?crop%5Bwidth%5D=800 800w"
sizes="(max-width: 600px) 400px, 800px"
alt="A landscape photo">
Fixed — brackets removed from URL design
If you can modify the server-side routing, restructuring the query parameters avoids the issue entirely:
<img
src="photo.jpg"
srcset="photo.jpg?crop_width=400 400w, photo.jpg?crop_width=800 800w"
sizes="(max-width: 600px) 400px, 800px"
alt="A landscape photo">
Incorrect — brackets with pixel density descriptors
The same problem occurs regardless of whether you use width descriptors or density descriptors:
<img
src="avatar.jpg"
srcset="avatar.jpg?size=[sm] 1x, avatar.jpg?size=[lg] 2x"
alt="User avatar">
Fixed — percent-encoded version
<img
src="avatar.jpg"
srcset="avatar.jpg?size=%5Bsm%5D 1x, avatar.jpg?size=%5Blg%5D 2x"
alt="User avatar">
Fixed — simplified query parameters
<img
src="avatar.jpg"
srcset="avatar.jpg?size=sm 1x, avatar.jpg?size=lg 2x"
alt="User avatar">
The HTML specification defines that the width and height attributes on <iframe> elements must contain a valid non-negative integer — that is, a string of one or more digits representing a number zero or greater (e.g., "0", "300", "600"). When one of these attributes is set to an empty string (width="" or height=""), the validator raises this error because an empty string cannot be parsed as a valid integer.
This commonly happens when a CMS, template engine, or JavaScript framework outputs an <iframe> with a dynamic dimension value that ends up being blank. It can also occur when developers remove the value but leave the attribute in place, or when copy-pasting embed code and accidentally clearing the value.
While most browsers will fall back to their default iframe dimensions (typically 300×150 pixels) when they encounter an empty value, relying on this behavior is not standards-compliant. Invalid attribute values can cause unpredictable rendering across different browsers, interfere with layout calculations, and make your markup harder to maintain. Assistive technologies may also have trouble determining the intended dimensions of the iframe.
How to fix it
You have a few options:
- Set a valid integer value. If you know the desired dimensions, specify them directly as non-negative integers. The values represent pixels.
- Remove the attribute entirely. If you don’t need to set dimensions via HTML attributes, remove the empty width or height attribute. The browser will apply its default size, or you can control sizing with CSS.
- Use CSS instead. For responsive designs or more flexible sizing, remove the HTML attributes and use CSS properties like width, height, max-width, or aspect-ratio.
Note that these attributes accept only plain integers — no units, no percentages, and no decimal points. For example, width="600" is valid, but width="600px" or width="100%" is not.
Examples
❌ Invalid: empty string values
<iframe src="https://example.com" width="" height=""></iframe>
Both width and height are set to empty strings, which are not valid non-negative integers.
✅ Fixed: specify valid integer values
<iframe src="https://example.com" width="600" height="400"></iframe>
✅ Fixed: remove the empty attributes
<iframe src="https://example.com"></iframe>
The browser will use its default dimensions (typically 300×150 pixels).
✅ Fixed: remove attributes and use CSS for sizing
<iframe src="https://example.com" style="width: 100%; height: 400px;"></iframe>
This approach is especially useful for responsive layouts where a fixed pixel width in HTML doesn’t make sense.
✅ Fixed: responsive iframe with CSS aspect ratio
<iframe
src="https://example.com/video"
style="width: 100%; aspect-ratio: 16 / 9; border: none;">
</iframe>
Using aspect-ratio in CSS lets the iframe scale responsively while maintaining its proportions, without needing width or height attributes at all.
The autocomplete attribute helps browsers automatically fill in form fields with previously saved user data. The HTML specification defines a strict set of valid autofill field names, and "company" is not among them. While “company” might seem like an intuitive choice, the spec uses "organization" to represent a company name, business name, or other organizational name associated with the person or address in the form.
Using an invalid autocomplete value means browsers won’t recognize the field’s purpose and cannot offer relevant autofill suggestions. This degrades the user experience — especially on mobile devices where autofill significantly speeds up form completion. It also impacts accessibility, as assistive technologies may rely on valid autocomplete tokens to help users understand and complete forms efficiently.
The full list of valid autofill field names is defined in the WHATWG HTML Living Standard. Some commonly used values include "name", "email", "tel", "street-address", "postal-code", "country", and "organization". When choosing a value, always refer to the specification rather than guessing a name that seems logical.
Examples
❌ Invalid: using "company" as an autocomplete value
<label for="company">Company Name</label>
<input type="text" id="company" name="company" autocomplete="company">
This triggers the validation error because "company" is not a recognized autofill field name.
✅ Valid: using "organization" instead
<label for="company">Company Name</label>
<input type="text" id="company" name="company" autocomplete="organization">
The value "organization" is the spec-defined autofill field name for “the company, organization, institution, or other entity associated with the person, address, or contact information in the other fields associated with this field.”
✅ Valid: using "organization" with a section and purpose
You can combine "organization" with other valid tokens for more specificity:
<label for="work-org">Employer</label>
<input type="text" id="work-org" name="employer" autocomplete="section-work organization">
This tells the browser that the field is for an organization name within a specific named section of the form, which is useful when a form collects information about multiple entities.
Common Autofill Field Names for Business Forms
Here are some valid autocomplete values you might use alongside "organization" in a business-related form:
- "organization" — company or organization name
- "organization-title" — job title (e.g., “Software Engineer”, “CEO”)
- "name" — full name of the contact person
- "email" — email address
- "tel" — telephone number
- "street-address" — full street address
Using the correct values ensures browsers can provide meaningful autofill suggestions, making your forms faster and easier to complete.
The aria-labelledby attribute is an IDREFS attribute, meaning its value must be a space-separated list of one or more id values that exist in the document. These referenced elements collectively provide the accessible name for the element. When the value is an empty string ("") or contains only whitespace, there are no valid ID references, which violates the IDREFS requirement defined in the WAI-ARIA and HTML specifications.
This issue commonly appears when templating systems or JavaScript frameworks conditionally set aria-labelledby but output an empty string when no label ID is available. It also occurs when developers add the attribute as a placeholder with the intention of filling it in later but forget to do so.
Why this matters
An empty aria-labelledby is problematic for several reasons:
- Accessibility: Screen readers rely on aria-labelledby to announce the accessible name of an element. An empty value can cause unpredictable behavior — some screen readers may ignore the SVG entirely, while others may fall back to reading unhelpful content or nothing at all. This leaves users who depend on assistive technology without a meaningful description of the graphic.
- Standards compliance: The W3C validator flags this as an error because the HTML specification requires IDREFS attributes to contain at least one non-whitespace character. Shipping invalid HTML can signal broader quality issues and may cause problems in strict parsing environments.
- Maintainability: An empty aria-labelledby is ambiguous. It’s unclear whether the developer intended the SVG to be decorative, forgot to add a reference, or encountered a bug in their templating logic.
How to fix it
Choose the approach that matches your intent:
- Reference a labeling element by ID: If the SVG conveys meaning, add a <title> element (or another visible text element) inside or near the SVG with a unique id, then set aria-labelledby to that id. IDs are case-sensitive, so ensure an exact match.
- Use aria-label instead: If you want to provide an accessible name directly as a text string without needing a separate element, replace aria-labelledby with aria-label.
- Remove the attribute: If the SVG already has an accessible name through other means (such as visible adjacent text or a <title> child that doesn’t need explicit referencing), simply remove the empty aria-labelledby.
- Mark as decorative: If the SVG is purely decorative and adds no information, remove aria-labelledby and add aria-hidden="true" so assistive technology skips it entirely.
When generating aria-labelledby dynamically, ensure your code omits the attribute entirely rather than outputting an empty value when no label ID is available.
Examples
❌ Empty aria-labelledby (triggers the error)
<svg role="img" aria-labelledby="">
<use href="#icon-star"></use>
</svg>
The empty string is not a valid IDREFS value, so the validator reports an error.
✅ Reference a <title> element by ID
<svg role="img" aria-labelledby="star-title">
<title id="star-title">Favorite</title>
<use href="#icon-star"></use>
</svg>
The aria-labelledby points to the <title> element’s id, giving the SVG a clear accessible name of “Favorite.”
✅ Use aria-label with a text string
<svg role="img" aria-label="Favorite">
<use href="#icon-star"></use>
</svg>
When you don’t need to reference another element, aria-label provides the accessible name directly as an attribute value.
✅ Reference multiple labeling elements
<svg role="img" aria-labelledby="star-title star-desc">
<title id="star-title">Favorite</title>
<desc id="star-desc">A five-pointed star icon</desc>
<use href="#icon-star"></use>
</svg>
The aria-labelledby value can include multiple space-separated IDs. The accessible name is constructed by concatenating the text content of the referenced elements in order.
✅ Decorative SVG (no accessible name needed)
<svg aria-hidden="true" focusable="false">
<use href="#icon-decorative-divider"></use>
</svg>
For purely decorative graphics, aria-hidden="true" removes the element from the accessibility tree. Adding focusable="false" prevents the SVG from receiving keyboard focus in older versions of Internet Explorer and Edge.
The aria-labelledby attribute accepts an IDREFS value — a space-separated list of one or more id values that reference other elements in the document. The validator expects each ID in the list to be non-empty and contain at least one non-whitespace character. When the attribute is set to an empty string (aria-labelledby=""), it violates this constraint and triggers the validation error.
This issue commonly arises in templating systems and JavaScript frameworks where a variable intended to hold an ID reference resolves to an empty string. For example, a template like aria-labelledby="{{ labelId }}" will produce an empty attribute if labelId is undefined or blank.
Why this matters
The aria-labelledby attribute is one of the highest-priority methods for computing an element’s accessible name. According to the accessible name computation algorithm, aria-labelledby overrides all other naming sources — including visible text content, aria-label, and the title attribute. When aria-labelledby is present but empty or broken, screen readers may calculate the link’s accessible name as empty, effectively making the link invisible or meaningless to assistive technology users. A link with no accessible name is a significant accessibility barrier: users cannot determine where the link goes or what it does.
Beyond accessibility, an empty aria-labelledby also signals invalid HTML according to both the WHATWG HTML living standard and the WAI-ARIA specification, which define the IDREFS type as requiring at least one valid token.
How to fix it
You have several options depending on your situation:
- Reference a valid ID — Point aria-labelledby to the id of an existing element whose text content should serve as the link’s accessible name.
- Remove the attribute and use visible link text — If the link already contains descriptive text, aria-labelledby is unnecessary.
- Use aria-label instead — For icon-only links where no visible label element exists, aria-label provides a concise accessible name directly on the element.
- Conditionally render the attribute — In templates, use conditional logic to omit aria-labelledby entirely when there’s no valid ID to reference, rather than rendering an empty value.
Examples
Invalid: empty aria-labelledby
This triggers the validation error because the attribute value contains no non-whitespace characters.
<a href="/report" aria-labelledby=""></a>
Invalid: whitespace-only aria-labelledby
A value containing only spaces is equally invalid — IDREFS requires at least one actual token.
<a href="/report" aria-labelledby=" "></a>
Fixed: referencing an existing element by id
The aria-labelledby attribute points to a <span> whose text content becomes the link’s accessible name.
<a href="/report" aria-labelledby="report-link-text">
<svg aria-hidden="true" viewBox="0 0 16 16"></svg>
</a>
<span id="report-link-text">View report</span>
Fixed: referencing multiple IDs
You can concatenate text from multiple elements by listing their IDs separated by spaces. The accessible name is built by joining the referenced text in order.
<span id="action">Learn more:</span>
<span id="subject">Apples</span>
<a href="/apples" aria-labelledby="action subject">
<svg aria-hidden="true" viewBox="0 0 16 16"></svg>
</a>
In this case, the computed accessible name is “Learn more: Apples”.
Fixed: using visible link text instead
When the link already contains descriptive text, no ARIA attribute is needed. This is the simplest and most robust approach.
<a href="/report">View report</a>
Fixed: using aria-label for an icon-only link
When there is no separate visible label element to reference, aria-label provides the accessible name directly.
<a href="/search" aria-label="Search">
<svg aria-hidden="true" viewBox="0 0 16 16"></svg>
</a>
Fixed: conditional rendering in a template
If you’re using a templating engine, conditionally include the attribute only when a value exists. The exact syntax varies by framework, but here’s the general idea:
<!-- Instead of always rendering the attribute: -->
<!-- <a href="/report" aria-labelledby="{{ labelId }}"> -->
<!-- Only render it when labelId has a value: -->
<!-- <a href="/report" {{#if labelId}}aria-labelledby="{{labelId}}"{{/if}}> -->
This prevents the empty-attribute problem at its source rather than patching it after the fact.
The WAI-ARIA specification defines strict ownership requirements for certain roles. The listitem role is one such role — it must be “owned by” an element with role="list" or role="group". “Owned by” means the listitem must be either a direct DOM child of the owning element, or explicitly associated with it via the aria-owns attribute.
This matters because screen readers and other assistive technologies rely on the accessibility tree to convey structure to users. When a screen reader encounters a properly structured list, it announces something like “list, 3 items” and lets the user navigate between items. Without the parent role="list", the individual items lose their list context — users won’t know how many items exist, where the list begins and ends, or that the items are related at all.
In most cases, the simplest and most robust fix is to use native HTML list elements (<ul> or <ol> with <li> children) instead of ARIA roles. Native elements have built-in semantics that don’t require additional attributes. Only use ARIA roles when native elements aren’t feasible — for example, when building a custom component where the visual layout prevents using standard list markup.
Examples
Incorrect: listitem without a parent list
These listitem elements are not contained within a role="list" or role="group" parent, so the validator reports an error.
<div role="listitem">Apples</div>
<div role="listitem">Bananas</div>
<div role="listitem">Cherries</div>
Correct: wrapping items in role="list"
Adding a parent container with role="list" establishes the required ownership relationship.
<div role="list">
<div role="listitem">Apples</div>
<div role="listitem">Bananas</div>
<div role="listitem">Cherries</div>
</div>
Correct: using native HTML list elements
Native <ul> and <li> elements implicitly carry the list and listitem roles without any ARIA attributes. This is the preferred approach.
<ul>
<li>Apples</li>
<li>Bananas</li>
<li>Cherries</li>
</ul>
Correct: using role="group" for nested sublists
The role="group" container is appropriate for grouping a subset of list items within a larger list, such as a nested sublist.
<div role="list">
<div role="listitem">Fruits
<div role="group">
<div role="listitem">Apples</div>
<div role="listitem">Bananas</div>
</div>
</div>
<div role="listitem">Vegetables
<div role="group">
<div role="listitem">Carrots</div>
<div role="listitem">Peas</div>
</div>
</div>
</div>
Note that role="group" should itself be nested inside a role="list" — it doesn’t replace the top-level list container, but rather serves as an intermediate grouping mechanism within one.
Correct: using aria-owns for non-descendant ownership
If the DOM structure prevents you from nesting the items directly inside the list container, you can use aria-owns to establish the relationship programmatically.
<div role="list" aria-owns="item-1 item-2 item-3"></div>
<!-- These items live elsewhere in the DOM -->
<div role="listitem" id="item-1">Apples</div>
<div role="listitem" id="item-2">Bananas</div>
<div role="listitem" id="item-3">Cherries</div>
This approach should be used sparingly, as it can create confusion if the visual order doesn’t match the accessibility tree order. Whenever possible, restructure your HTML so the items are actual descendants of the list container.
The HTML5 specification mandates UTF-8 as the only permitted character encoding for web documents declared via <meta> tags. Legacy encodings such as windows-1251 (a Cyrillic character encoding), iso-8859-1, shift_jis, and others are no longer valid values in HTML5 <meta> declarations. This restriction exists because UTF-8 is a universal encoding that can represent virtually every character from every writing system, eliminating the interoperability problems that plagued the web when dozens of competing encodings were in use.
When the validator encounters content="text/html; charset=windows-1251" on a <meta> element, it flags it because the charset= portion must be followed by utf-8 — no other value is accepted. This applies whether you use the longer <meta http-equiv="Content-Type"> syntax or the shorter <meta charset> syntax.
Why this matters
- Standards compliance: The WHATWG HTML Living Standard explicitly requires utf-8 as the character encoding when declared in a <meta> tag. Non-conforming encodings will trigger validation errors.
- Internationalization: UTF-8 supports all Unicode characters, making your pages work correctly for users across all languages, including Cyrillic text that windows-1251 was originally designed for.
- Security: Legacy encodings can introduce security vulnerabilities, including certain cross-site scripting (XSS) attack vectors that exploit encoding ambiguity.
- Browser consistency: While browsers may still recognize legacy encodings, relying on them can cause mojibake (garbled text) when there’s a mismatch between the declared and actual encoding.
How to fix it
- Update the <meta> tag to declare utf-8 as the charset.
- Re-save your file in UTF-8 encoding using your text editor or IDE. Most modern editors support this — look for an encoding option in “Save As” or in the status bar.
- Verify your server configuration. If your server sends a Content-Type HTTP header with a different encoding, the server header takes precedence over the <meta> tag. Make sure both agree on UTF-8.
- Convert your content. If your text was originally written in windows-1251, you may need to convert it to UTF-8. Tools like iconv on the command line can help: iconv -f WINDOWS-1251 -t UTF-8 input.html > output.html.
Examples
❌ Incorrect: Using windows-1251 charset
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
✅ Correct: Using utf-8 with http-equiv syntax
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
✅ Correct: Using the shorter <meta charset> syntax (preferred in HTML5)
<meta charset="utf-8">
Full document example
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Пример страницы</title>
</head>
<body>
<p>Текст на русском языке в кодировке UTF-8.</p>
</body>
</html>
The shorter <meta charset="utf-8"> syntax is generally preferred in HTML5 documents because it’s more concise and achieves the same result. Whichever syntax you choose, place the charset declaration within the first 1024 bytes of your document — ideally as the very first element inside <head> — so browsers can detect the encoding as early as possible.
XML processing instructions are a feature of XML, not HTML. They begin with <? and end with ?>, and are used in XML documents to carry instructions for applications processing the document. The most common example is the XML declaration: <?xml version="1.0" encoding="UTF-8"?>. While these are perfectly valid in XML, the HTML parser does not recognize them. When the parser encounters <?, it doesn’t know how to handle it and treats it as a bogus comment, which leads to unexpected behavior and this validation error.
This matters for several reasons. First, standards compliance — HTML5 has a clearly defined parsing algorithm, and processing instructions are not part of it. Second, browser behavior becomes unpredictable — different browsers may handle the unexpected <? content differently, potentially exposing raw code or breaking your layout. Third, if server-side code like PHP leaks into the output, it can expose sensitive logic or configuration details to end users.
There are three common causes of this error:
1. Inlining SVG files with their XML declaration. When you copy the contents of an .svg file and paste it directly into HTML, the file often starts with an XML declaration and possibly a <?xml-stylesheet?> processing instruction. These must be removed — only the <svg> element and its children should be included.
2. Unprocessed server-side code. Languages like PHP use <?php ... ?> (or the short tag <? ... ?>) syntax. If the server isn’t configured to process PHP files, or if a .html file contains PHP code without being routed through the PHP interpreter, the raw <?php tags end up in the HTML sent to the browser.
3. Copy-pasting XML content. Other XML-based formats (like RSS feeds, XHTML fragments, or MathML with XML declarations) may include processing instructions that aren’t valid in HTML.
How to Fix It
- For inline SVGs: Open the SVG file in a text editor and copy only the <svg>...</svg> element. Remove the <?xml ...?> declaration and any other processing instructions that precede the <svg> tag.
- For PHP or other server-side code: Ensure your server is properly configured to process the files. Check that the file extension matches what the server expects (e.g., .php for PHP files). Verify that the server-side language is installed and running.
- For other XML content: Strip out any <?...?> processing instructions before embedding the content in HTML.
Examples
❌ Inline SVG with XML declaration
<body>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="blue" />
</svg>
</body>
The <?xml version="1.0" encoding="UTF-8"?> line triggers the error.
✅ Inline SVG without XML declaration
<body>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="blue" />
</svg>
</body>
❌ Unprocessed PHP code in HTML output
<body>
<h1>Welcome</h1>
<p><?php echo "Hello, World!"; ?></p>
</body>
If the server doesn’t process the PHP, the raw <?php ... ?> ends up in the HTML and triggers this error.
✅ Properly processed output (or static HTML equivalent)
<body>
<h1>Welcome</h1>
<p>Hello, World!</p>
</body>
❌ SVG with an XML stylesheet processing instruction
<body>
<?xml-stylesheet type="text/css" href="style.css"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
<rect width="50" height="50" fill="red" />
</svg>
</body>
✅ SVG without the processing instruction
<body>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
<rect width="50" height="50" fill="red" />
</svg>
</body>
In HTML, use standard <link> or <style> elements in the <head> for stylesheets instead of XML processing instructions.
The validator error occurs when an element such as an a, button, or custom widget includes aria-controls="" (empty) or whitespace-only. The aria-controls attribute takes one or more space-separated id values (IDREFS). Each referenced id must exist exactly once in the same document. Leaving it empty violates the ARIA and HTML requirements and provides no usable relationship for assistive technologies.
Why this matters:
- Accessibility: Screen readers rely on aria-controls to announce relationships between controls and controlled regions (e.g., a toggle and its panel). An empty value misleads AT or adds noise.
- Standards compliance: HTML and ARIA require at least one non-whitespace id. Empty values cause validation failures.
- Robustness: Incorrect references can confuse scripts and future maintainers, and break behavior when IDs change.
How to fix it:
- Only add aria-controls when the element truly controls another region (show/hide, sort, update).
- Ensure the controlled element has a unique id.
- Set aria-controls to that id (or multiple space-separated IDs).
- Keep the reference in sync if IDs change.
- If nothing is controlled, remove aria-controls entirely.
Examples
Invalid: empty aria-controls (triggers the error)
<a href="#" aria-controls="">Toggle details</a>
Valid: control a single region
<div id="details-panel" hidden>
Some details...
</div>
<a href="#details-panel" aria-controls="details-panel">Toggle details</a>
Valid: control multiple regions (space-separated IDs)
<section id="filters" hidden>...</section>
<section id="results" hidden>...</section>
<button type="button" aria-controls="filters results">Show filters and results</button>
Valid: remove when not needed
<a href="#">Toggle details</a>
Minimal complete document with proper usage
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>aria-controls Example</title>
</head>
<body>
<button type="button" aria-controls="info" aria-expanded="false">Toggle info</button>
<div id="info" hidden>
Extra information.
</div>
<script>
const btn = document.querySelector('button');
const panel = document.getElementById(btn.getAttribute('aria-controls'));
btn.addEventListener('click', () => {
const expanded = btn.getAttribute('aria-expanded') === 'true';
btn.setAttribute('aria-expanded', String(!expanded));
panel.hidden = expanded;
});
</script>
</body>
</html>
Tips:
- Use aria-controls for functional relationships (control affects content), not just visual proximity.
- Combine with aria-expanded when toggling visibility to convey state.
- Verify that every id in aria-controls exists and is unique; avoid dynamic mismatches created by templating or component reuse.
The WAI-ARIA specification defines strict ownership requirements for certain roles. The menuitem role represents an option in a set of choices and is only meaningful when it exists within the context of a menu. When a menuitem appears outside of a menu or menubar, screen readers and other assistive technologies have no way to determine that it belongs to a menu widget. They cannot announce the total number of items, provide keyboard navigation between items, or convey the menu’s hierarchical structure to the user.
This requirement follows the concept of required owned elements and required context roles in ARIA. Just as a <li> element belongs inside a <ul> or <ol>, a menuitem belongs inside a menu or menubar. The relationship can be established in two ways:
- DOM nesting — the menuitem element is a DOM descendant of the menu or menubar element.
- The aria-owns attribute — the menu or menubar element uses aria-owns to reference the menuitem by its id, establishing ownership even when the elements aren’t nested in the DOM.
It’s important to note that ARIA menu roles are intended for application-style menus — the kind you’d find in a desktop application (e.g., File, Edit, View menus). They are not meant for standard website navigation. For typical site navigation, use semantic HTML elements like <nav> with <ul>, <li>, and <a> elements instead.
How to fix it
- Identify every element with role="menuitem" in your markup.
- Ensure each one is contained within an element that has role="menu" or role="menubar", either through DOM nesting or via aria-owns.
-
Choose the correct parent role:
- Use role="menubar" for a persistent, typically horizontal menu bar (like a desktop application’s top-level menu).
- Use role="menu" for a popup or dropdown menu that contains a group of menu items.
- If you’re using menus for site navigation, consider removing the ARIA menu roles entirely and using semantic HTML (<nav>, <ul>, <li>, <a>) instead.
Examples
Incorrect — menuitem without a menu context
This triggers the validator error because the menuitem elements have no parent menu or menubar:
<div>
<div role="menuitem">Cut</div>
<div role="menuitem">Copy</div>
<div role="menuitem">Paste</div>
</div>
Correct — menuitem inside a menu
Wrapping the items in an element with role="menu" resolves the issue:
<div role="menu">
<div role="menuitem" tabindex="0">Cut</div>
<div role="menuitem" tabindex="-1">Copy</div>
<div role="menuitem" tabindex="-1">Paste</div>
</div>
Correct — menuitem inside a menubar
For a persistent horizontal menu bar with application-style actions:
<div role="menubar">
<div role="menuitem" tabindex="0">File</div>
<div role="menuitem" tabindex="-1">Edit</div>
<div role="menuitem" tabindex="-1">View</div>
</div>
Correct — nested menus with dropdown submenus
A menubar with a dropdown menu containing additional menuitem elements:
<div role="menubar">
<div role="menuitem" tabindex="0" aria-haspopup="true" aria-expanded="false">
File
<div role="menu">
<div role="menuitem" tabindex="-1">New</div>
<div role="menuitem" tabindex="-1">Open</div>
<div role="menuitem" tabindex="-1">Save</div>
</div>
</div>
<div role="menuitem" tabindex="-1" aria-haspopup="true" aria-expanded="false">
Edit
<div role="menu">
<div role="menuitem" tabindex="-1">Cut</div>
<div role="menuitem" tabindex="-1">Copy</div>
<div role="menuitem" tabindex="-1">Paste</div>
</div>
</div>
</div>
Correct — using aria-owns for ownership without DOM nesting
When the menuitem elements cannot be nested inside the menu in the DOM (e.g., due to layout constraints), use aria-owns to establish the relationship:
<div role="menu" aria-owns="item-cut item-copy item-paste"></div>
<div role="menuitem" id="item-cut" tabindex="0">Cut</div>
<div role="menuitem" id="item-copy" tabindex="-1">Copy</div>
<div role="menuitem" id="item-paste" tabindex="-1">Paste</div>
Better alternative — use semantic HTML for site navigation
If you’re building standard website navigation (not an application-style menu), avoid ARIA menu roles altogether:
<nav aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
This approach is simpler, more accessible by default, and doesn’t trigger the validator warning. Reserve role="menu", role="menubar", and role="menuitem" for true application-style menus that implement full keyboard interaction patterns as described in the ARIA Authoring Practices Guide.
The <label> element associates descriptive text with a specific form control, enabling users to click the label to focus or activate the associated input. The for attribute creates this link by referencing the id of the target form control. When the referenced id doesn’t correspond to a valid, non-hidden form control, the label becomes orphaned — it isn’t associated with anything meaningful.
The W3C validator raises this error in several scenarios:
- The for attribute references an id that doesn’t exist in the document.
- The for attribute references an element that isn’t a labelable element (such as a <div> or <span>).
- The for attribute references an <input type="hidden">, which is not a visible form control and cannot be labeled.
- There’s a typo or mismatch between the for value and the intended element’s id.
Labelable elements in HTML include <input> (except type="hidden"), <select>, <textarea>, <button>, <meter>, <output>, and <progress>.
This matters for accessibility because screen readers rely on the for/id association to announce what each form control represents. Without a valid association, users who depend on assistive technology may not understand what a form field is asking for. It also impacts usability — a properly linked label expands the clickable area for the form control, making it easier to interact with, especially on touch devices and for users with motor impairments.
To fix this issue, verify that the for attribute value exactly matches the id of a visible, labelable form control. If you’re labeling a hidden input, consider whether the label is necessary at all (hidden inputs are not user-facing). If the target element isn’t a form control, either change it to the appropriate form element or use a different approach like aria-labelledby.
Examples
❌ for references a non-existent ID
<label for="username">Username</label>
<input id="user-name" type="text">
The for value "username" doesn’t match the input’s id of "user-name".
✅ Fixed: matching for and id
<label for="username">Username</label>
<input id="username" type="text">
❌ for references a hidden input
<label for="token">Token</label>
<input id="token" type="hidden" value="abc123">
An <input type="hidden"> is not a visible form control and cannot be labeled.
✅ Fixed: remove the unnecessary label
<input id="token" type="hidden" value="abc123">
Hidden inputs don’t need labels since users never interact with them directly.
❌ for references a non-labelable element
<label for="info">Information</label>
<div id="info">Some details here</div>
A <div> is not a labelable element, so the for association is invalid.
✅ Fixed: use a proper form control or implicit labeling
<label for="info">Information</label>
<textarea id="info"></textarea>
❌ for references an element inside another form control
<label for="opt">Choose one</label>
<select>
<option id="opt" value="a">Option A</option>
</select>
An <option> element is not a labelable element. The label should point to the <select>.
✅ Fixed: reference the <select> element
<label for="choice">Choose one</label>
<select id="choice">
<option value="a">Option A</option>
</select>
Using implicit labeling as an alternative
Instead of using the for attribute, you can wrap the form control inside the <label> element. This creates an implicit association without needing for or id at all:
<label>
Age
<input type="number">
</label>
This approach avoids the for/id mismatch problem entirely and is equally valid for accessibility.
The srcset attribute lets you provide multiple image sources so the browser can select the most appropriate one. There are two types of descriptors you can use in srcset: pixel density descriptors (e.g., 1x, 2x) and width descriptors (e.g., 400w, 800w). When you use pixel density descriptors, the browser already knows the relationship between each source — it simply picks the one matching the device’s pixel ratio. But width descriptors work differently. They tell the browser the intrinsic pixel width of each image file, and the browser then needs to know how wide the image will actually be rendered on screen to calculate which file is the best fit. That’s exactly what the sizes attribute provides.
The sizes attribute accepts a comma-separated list of media conditions paired with length values, plus a default length. For example, sizes="(max-width: 600px) 100vw, 50vw" tells the browser: “If the viewport is 600px wide or less, this image will occupy 100% of the viewport width; otherwise, it will occupy 50%.” Armed with this information and the width descriptors in srcset, the browser can do the math and download only the most suitable image — before CSS or layout has even been calculated.
Why this matters
- Standards compliance: The HTML specification requires sizes whenever srcset uses width descriptors. Omitting it produces invalid HTML.
- Correct image selection: Without sizes, browsers fall back to assuming the image will be 100vw wide, which often leads to downloading unnecessarily large images on desktop layouts where the image is much smaller than the full viewport.
- Performance: Serving oversized images wastes bandwidth and slows page load. A proper sizes attribute ensures the browser downloads the smallest sufficient image.
- Predictable behavior: Relying on the browser’s fallback assumption (100vw) makes your responsive images behave inconsistently and defeats the purpose of providing multiple candidates.
How to fix it
- Identify every img (or source) element that uses width descriptors in srcset.
- Determine how wide the image will be displayed at different viewport sizes. You can inspect this with your browser’s developer tools or by reviewing your CSS.
- Add a sizes attribute that describes those widths using media conditions and CSS length values like px, vw, em, or calc() expressions.
Examples
Incorrect — missing sizes with width descriptors
<img
src="photo-400.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
alt="A mountain landscape">
This triggers the validation error because the browser sees width descriptors (400w, 800w, 1200w) but has no sizes attribute to determine the image’s rendered width.
Correct — sizes attribute added
<img
src="photo-400.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
sizes="(max-width: 600px) 100vw, (max-width: 1000px) 50vw, 600px"
alt="A mountain landscape">
Here, the sizes attribute tells the browser:
- On viewports up to 600px wide, the image fills 100% of the viewport.
- On viewports between 601px and 1000px, the image fills 50% of the viewport.
- On larger viewports, the image is displayed at a fixed 600px width.
Correct — pixel density descriptors (no sizes needed)
<img
src="logo-1x.png"
srcset="logo-1x.png 1x, logo-2x.png 2x"
alt="Company logo">
When using pixel density descriptors (1x, 2x) instead of width descriptors, the sizes attribute is not required. The browser simply matches the descriptor to the device’s pixel ratio.
Correct — using sizes with a <picture> element
<picture>
<source
srcset="hero-400.webp 400w, hero-800.webp 800w"
sizes="(max-width: 800px) 100vw, 800px"
type="image/webp">
<img
src="hero-800.jpg"
srcset="hero-400.jpg 400w, hero-800.jpg 800w"
sizes="(max-width: 800px) 100vw, 800px"
alt="Hero banner">
</picture>
The sizes attribute is required on both the source and img elements when either uses width descriptors in its srcset.
The HTML specification mandates that documents must be encoded in UTF-8. This requirement exists because UTF-8 is the universal character encoding that supports virtually every character from every writing system in the world. Older encodings like windows-1252, iso-8859-1, or shift_jis only support a limited subset of characters and can cause text to display incorrectly — showing garbled characters or question marks — especially for users in different locales or when content includes special symbols, accented letters, or emoji.
When the validator encounters charset=windows-1252 in your <meta> tag, it flags this as an error because the HTML living standard (WHATWG) explicitly states that the character encoding declaration must specify utf-8 as the encoding. This isn’t just a stylistic preference — browsers and other tools rely on this declaration to correctly interpret the bytes in your document. Using a non-UTF-8 encoding can lead to security vulnerabilities (such as encoding-based XSS attacks) and accessibility issues when assistive technologies misinterpret characters.
To fix this issue, take two steps:
- Update the <meta> tag to declare utf-8 as the charset.
- Re-save your file with UTF-8 encoding. Most modern code editors (VS Code, Sublime Text, etc.) let you choose the encoding when saving — look for an option like “Save with Encoding” or check the status bar for the current encoding. If your file was originally in windows-1252, simply changing the <meta> tag without re-encoding the file could cause existing special characters to display incorrectly.
The HTML spec also recommends using the shorter <meta charset="utf-8"> form rather than the longer <meta http-equiv="Content-Type" ...> pragma directive, as it’s simpler and achieves the same result. Either form is valid, but the charset declaration must appear within the first 1024 bytes of the document.
Examples
Incorrect: Using windows-1252 charset
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
This triggers the validator error because the charset is not utf-8.
Correct: Using the short charset declaration (recommended)
<meta charset="utf-8">
Correct: Using the http-equiv pragma directive with utf-8
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
Full document example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
Note that the <meta charset="utf-8"> tag should be the first element inside <head>, before any other elements (including <title>), so the browser knows the encoding before it starts parsing the rest of the document.
Ready to validate your sites?
Start your free trial today.