HTML Guides
Learn how to identify and fix common HTML validation errors flagged by the W3C Validator — so your pages are standards-compliant and render correctly across every browser. Also check our Accessibility Guides.
The 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%20is 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=""orheight=""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
inputmodewith the appropriatetypeandpatternattributes to ensure proper validation and semantics, sinceinputmodealone 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
autocompleteattribute 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
readonlyattribute 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
langattribute 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: autoproperty depends on thelangattribute to apply language-appropriate hyphenation rules. - Search engines: Search engines use the
langattribute 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.
The aria-valuenow attribute requires a valid numeric value and cannot be an empty string.
The aria-valuenow attribute is used on range-type widgets like progress bars, sliders, and spinbuttons to indicate the current value. It must be a valid floating point number — for example, 0, 50, or 3.5. An empty string ("") is not a number, so the validator rejects it.
If the current value is unknown or indeterminate, you should remove the aria-valuenow attribute entirely rather than setting it to an empty string. This is common with indeterminate progress bars where the completion percentage isn't known yet.
When present, aria-valuenow should fall between aria-valuemin and aria-valuemax. All three attributes work together to communicate the state of a range widget to assistive technologies.
Bad Example
<div role="progressbar"
aria-valuenow=""
aria-valuemin="0"
aria-valuemax="100">
Loading...
</div>
Good Example — Known Value
<div role="progressbar"
aria-valuenow="50"
aria-valuemin="0"
aria-valuemax="100">
50% complete
</div>
Good Example — Indeterminate State
<div role="progressbar"
aria-valuemin="0"
aria-valuemax="100">
Loading...
</div>
In the indeterminate example, omitting aria-valuenow tells assistive technologies that the progress is ongoing but the exact value is not known.
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
idvalues with standard fragment syntax using<use>elements. - Use an external file instead of a
data:URI. Standard URLs likeicons.svg#homefully 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 does not accept "telephone" as a valid value — the correct value is "tel".
The autocomplete attribute helps browsers autofill form fields with previously saved user data. Each field type has a specific token defined in the HTML specification. For phone numbers, the valid token is "tel", not "telephone". Other related tokens include "tel-country-code", "tel-area-code", "tel-local", and "tel-extension" for more granular phone number parts.
Using the correct token ensures that browsers can properly suggest stored phone numbers to users, improving the form-filling experience.
Invalid Example
<label for="phone">Phone number</label>
<input type="tel" id="phone" name="phone" autocomplete="telephone">
Valid Example
<label for="phone">Phone number</label>
<input type="tel" id="phone" name="phone" autocomplete="tel">
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
autocompletevalues 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
hrefvalue, 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
hrefvalues contain valid URLs. A tab in the domain makes the URL syntactically invalid. - Accessibility: Screen readers and assistive technologies parse
hrefvalues 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
hrefvalues. In many editors, you can use a regex search for\twithin 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
An element with role="tab" must sit inside an element with role="tablist", or be claimed by one through the aria-owns attribute. When a tab lives outside a tablist, screen readers can't tell how many tabs are in the group or which one is selected, so the whole tab interface breaks for people who rely on assistive technology. To fix it, place your role="tab" elements inside a role="tablist" container.
The tab role only carries meaning inside a tablist. A tab controls the visibility of a matching role="tabpanel", and the tablist groups the tabs together so assistive technologies can announce position (for example, "tab 2 of 4") and move focus between tabs with the arrow keys. Take the tab out of its tablist and that grouping disappears, leaving the screen reader with a button it can't place in any structure.
The W3C validator emits this message in two slightly different wordings depending on its version: an element with the “role” value “tablist” and an element with “role=tablist”. Both report the same problem, and the fix is identical.
There are two ways to establish the required relationship. Nesting the tabs directly inside the tablist is the common one. When your layout or framework makes that nesting impossible, point aria-owns from the tablist at the tab ids instead.
Invalid example
Here the role="tab" buttons are siblings of the tablist rather than children of it, so no tablist contains or owns them.
<div class="tabs">
<div role="tablist" aria-label="Account settings"></div>
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">
Profile
</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2">
Billing
</button>
</div>
Valid example
Moving the tabs inside the role="tablist" element gives them the parent they need.
<div class="tabs">
<div role="tablist" aria-label="Account settings">
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1" tabindex="0">
Profile
</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2" tabindex="-1">
Billing
</button>
</div>
<div id="panel-1" role="tabpanel" aria-labelledby="tab-1">
<p>Profile settings</p>
</div>
<div id="panel-2" role="tabpanel" aria-labelledby="tab-2" hidden>
<p>Billing settings</p>
</div>
</div>
If you genuinely can't nest the tabs in the DOM, keep them where they are and add aria-owns="tab-1 tab-2" to the role="tablist" element so assistive technologies still treat the list as the owner of those tabs.
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%5Band 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
widthorheightattribute. 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, oraspect-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-labelledbyto 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-labelledbyis 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 uniqueid, then setaria-labelledbyto thatid. IDs are case-sensitive, so ensure an exact match. - Use
aria-labelinstead: If you want to provide an accessible name directly as a text string without needing a separate element, replacearia-labelledbywitharia-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 emptyaria-labelledby. - Mark as decorative: If the SVG is purely decorative and adds no information, remove
aria-labelledbyand addaria-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-labelledbyto theidof 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-labelledbyis unnecessary. - Use
aria-labelinstead — For icon-only links where no visible label element exists,aria-labelprovides a concise accessible name directly on the element. - Conditionally render the attribute — In templates, use conditional logic to omit
aria-labelledbyentirely 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-8as 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-1251was 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 declareutf-8as 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-TypeHTTP 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 likeiconvon 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.,
.phpfor 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.
A meta element with http-equiv="Content-Type" must declare utf-8 as the character encoding. Other encodings like Shift_JIS are not allowed in HTML5.
The HTML specification requires all documents to be encoded in UTF-8. The meta element's content attribute, when used with http-equiv="Content-Type", must contain exactly text/html; charset=utf-8. Legacy encodings such as Shift_JIS, EUC-JP, ISO-8859-1, and others are non-conforming.
If the document actually uses Shift_JIS encoding, it needs to be converted to UTF-8 first. Most text editors and IDEs can re-save a file in a different encoding. After converting the file, update the meta declaration to reference UTF-8.
The shorter <meta charset="utf-8"> form is equivalent and generally preferred because it is simpler.
HTML examples
Invalid encoding declaration
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
Fixed using the short form
<meta charset="utf-8">
Fixed using the http-equiv form
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
The aria-controls attribute on a button element has an empty value, but it requires at least one valid ID reference.
The aria-controls attribute accepts a space-separated list of id values that point to the elements controlled by the current element. When a screen reader encounters a button with aria-controls, it can offer the user a way to navigate to the controlled element. An empty string ("") is not a valid IDREF, so the W3C validator rejects it.
This often happens when a template or JavaScript framework sets aria-controls before the target element's id is known, or when the value is conditionally generated and falls through to an empty string.
There are two ways to fix this: either set aria-controls to a valid id that exists in the document, or remove the attribute entirely when no target is available. An absent aria-controls attribute is perfectly fine and preferable to an empty one.
HTML examples
Invalid: empty aria-controls
<button aria-controls="" aria-expanded="false">
Toggle menu
</button>
<nav id="main-menu" hidden>
<a href="/about">About</a>
</nav>
Fixed: aria-controls references a valid ID
<button aria-controls="main-menu" aria-expanded="false">
Toggle menu
</button>
<nav id="main-menu" hidden>
<a href="/about">About</a>
</nav>
If the controlled element does not exist yet or the ID is not available, remove the attribute:
<button aria-expanded="false">
Toggle menu
</button>
JavaScript can add aria-controls later, once the target element and its id are present in the DOM.
The aria-controls attribute establishes a programmatic relationship between an interactive element (like a button or link) and the content it controls. Assistive technologies such as screen readers use this relationship to help users navigate between a control and the region it affects—for example, a button that toggles a panel or a tab that switches visible content. When the attribute is present but empty (aria-controls=""or aria-controls=" "), it signals a broken relationship: the browser and assistive technology expect a target element but find nothing.
This issue commonly occurs when aria-controls is added by a JavaScript framework or template before the target element's id is known, leaving behind an empty placeholder. It can also happen when a CMS or component library outputs the attribute unconditionally, even when no controlled region exists.
Why this matters
- Accessibility: Screen readers may announce that a control is associated with another element, but an empty reference leads nowhere. This creates a confusing or misleading experience for users who rely on assistive technology.
- Standards compliance: The HTML specification defines
aria-controlsas an IDREFS attribute, meaning its value must contain one or more space-separated tokens, each matching theidof an element in the same document. An empty value is invalid per both the WAI-ARIA specification and the HTML standard. - Maintainability: Empty or placeholder ARIA attributes are a sign of incomplete implementation. They can mask bugs in dynamic UIs where the controlled element was supposed to be rendered but wasn't, or where an
idwas accidentally removed during refactoring.
How to fix it
- If the element controls another region, set
aria-controlsto theidof that region. Make sure the target element exists in the DOM and has a uniqueid. - If the element doesn't control anything, remove the
aria-controlsattribute entirely. An absent attribute is always better than an empty one. - For dynamically rendered content, only add
aria-controlsafter the target element and itsidare present in the DOM. If using a framework, conditionally render the attribute. - Keep references in sync. If you rename or remove an
id, update everyaria-controlsthat references it.
Examples
Invalid: empty aria-controls
This triggers the validator error because the attribute value contains no id reference.
<a href="#" aria-controls="">Toggle details</a>
Invalid: whitespace-only value
A value with only spaces is also invalid—IDREFS requires at least one non-whitespace token.
<button type="button" aria-controls=" ">Open menu</button>
Fixed: provide a valid id reference
Point aria-controls to the id of the element being controlled.
<button type="button" aria-controls="details-panel" aria-expanded="false">
Toggle details
</button>
<div id="details-panel" hidden>
<p>Here are some additional details.</p>
</div>
Fixed: controlling multiple regions
You can reference multiple id values separated by spaces. Each id must correspond to an element in the document.
<button type="button" aria-controls="filters results">
Show filters and results
</button>
<section id="filters" hidden>
<p>Filter options...</p>
</section>
<section id="results" hidden>
<p>Search results...</p>
</section>
Fixed: remove the attribute when not needed
If the element doesn't actually control another region, simply omit aria-controls.
<a href="/details">View details</a>
Complete document with proper toggle behavior
This example shows a working toggle pattern combining aria-controls with aria-expanded for full accessibility.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>aria-controls Toggle Example</title>
</head>
<body>
<button type="button" aria-controls="info-panel" aria-expanded="false">
Toggle info
</button>
<div id="info-panel" hidden>
<p>Extra information goes here.</p>
</div>
<script>
const btn = document.querySelector('button');
const panel = document.getElementById('info-panel');
btn.addEventListener('click', () => {
const expanded = btn.getAttribute('aria-expanded') === 'true';
btn.setAttribute('aria-expanded', String(!expanded));
panel.hidden = expanded;
});
</script>
</body>
</html>
Conditional rendering in a framework
When using a templating system or JavaScript framework, only render aria-controls when the target id is available. Here's a conceptual example:
<!-- Good: only add the attribute when targetId has a value -->
<button type="button" aria-controls="sidebar-nav" aria-expanded="false">
Menu
</button>
<nav id="sidebar-nav" hidden>
<ul>
<li><a href="/">Home</a></li>
</ul>
</nav>
<!-- Bad: template outputs an empty value when targetId is undefined -->
<!-- <button type="button" aria-controls="">Menu</button> -->
In frameworks like React, you can conditionally spread the attribute: use aria-controls={targetId || undefined} so that when the value is empty, the attribute is omitted from the rendered HTML entirely rather than being set to an empty string.
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