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 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
<inputtype="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"
<inputtype="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:
<inputtype="text"name="firstName"autocomplete="given-name">
Fixed: common valid autocomplete values in a form
<formmethod="post"action="/register">
<labelfor="name">Full Name</label>
<inputtype="text"id="name"name="name"autocomplete="name">
<labelfor="email">Email</label>
<inputtype="email"id="email"name="email"autocomplete="email">
<labelfor="newpass">Password</label>
<inputtype="password"id="newpass"name="password"autocomplete="new-password">
<labelfor="tel">Phone</label>
<inputtype="tel"id="tel"name="phone"autocomplete="tel">
<buttontype="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 -->
<ahref="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
<ahref="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:
<ahref="https:// example.com/page">Visit Example</a>
example.com/page
✅ Correct: URL on a single line with no embedded whitespace
<ahref="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
consturl=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%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
<iframesrc="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
<iframesrc="https://example.com"width="600"height="400"></iframe>
✅ Fixed: remove the empty attributes
<iframesrc="https://example.com"></iframe>
The browser will use its default dimensions (typically 300×150 pixels).
✅ Fixed: remove attributes and use CSS for sizing
<iframesrc="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
<labelfor="company">Company Name</label>
<inputtype="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
<labelfor="company">Company Name</label>
<inputtype="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:
<labelfor="work-org">Employer</label>
<inputtype="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)
<svgrole="img"aria-labelledby="">
<usehref="#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
<svgrole="img"aria-labelledby="star-title">
<titleid="star-title">Favorite</title>
<usehref="#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
<svgrole="img"aria-label="Favorite">
<usehref="#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
<svgrole="img"aria-labelledby="star-title star-desc">
<titleid="star-title">Favorite</title>
<descid="star-desc">A five-pointed star icon</desc>
<usehref="#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)
<svgaria-hidden="true"focusable="false">
<usehref="#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.
<ahref="/report"aria-labelledby=""></a>
Invalid: whitespace-only aria-labelledby
A value containing only spaces is equally invalid — IDREFS requires at least one actual token.
<ahref="/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.
<ahref="/report"aria-labelledby="report-link-text">
<svgaria-hidden="true"viewBox="0 0 16 16"></svg>
</a>
<spanid="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.
<spanid="action">Learn more:</span>
<spanid="subject">Apples</span>
<ahref="/apples"aria-labelledby="action subject">
<svgaria-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.
<ahref="/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.
<ahref="/search"aria-label="Search">
<svgaria-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.
<divrole="listitem">Apples</div>
<divrole="listitem">Bananas</div>
<divrole="listitem">Cherries</div>
Correct: wrapping items in role="list"
Adding a parent container with role="list" establishes the required ownership relationship.
<divrole="list">
<divrole="listitem">Apples</div>
<divrole="listitem">Bananas</div>
<divrole="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.
<divrole="list">
<divrole="listitem">Fruits
<divrole="group">
<divrole="listitem">Apples</div>
<divrole="listitem">Bananas</div>
</div>
</div>
<divrole="listitem">Vegetables
<divrole="group">
<divrole="listitem">Carrots</div>
<divrole="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.
<divrole="list"aria-owns="item-1 item-2 item-3"></div>
<!-- These items live elsewhere in the DOM -->
<divrole="listitem"id="item-1">Apples</div>
<divrole="listitem"id="item-2">Bananas</div>
<divrole="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
<metahttp-equiv="Content-Type"content="text/html; charset=windows-1251">
✅ Correct: Using utf-8 with http-equiv syntax
<metahttp-equiv="Content-Type"content="text/html; charset=utf-8">
✅ Correct: Using the shorter <meta charset> syntax (preferred in HTML5)
<metacharset="utf-8">
Full document example
<!DOCTYPE html>
<htmllang="ru">
<head>
<metacharset="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"?>
<svgxmlns="http://www.w3.org/2000/svg"viewBox="0 0 100 100">
<circlecx="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>
<svgxmlns="http://www.w3.org/2000/svg"viewBox="0 0 100 100">
<circlecx="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"?>
<svgxmlns="http://www.w3.org/2000/svg"viewBox="0 0 50 50">
<rectwidth="50"height="50"fill="red"/>
</svg>
</body>
✅ SVG without the processing instruction
<body>
<svgxmlns="http://www.w3.org/2000/svg"viewBox="0 0 50 50">
<rectwidth="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
<metahttp-equiv="Content-Type"content="text/html; charset=Shift_JIS">
Fixed using the short form
<metacharset="utf-8">
Fixed using the http-equiv form
<metahttp-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
<buttonaria-controls=""aria-expanded="false">
Toggle menu
</button>
<navid="main-menu"hidden>
<ahref="/about">About</a>
</nav>
Fixed: aria-controls references a valid ID
<buttonaria-controls="main-menu"aria-expanded="false">
Toggle menu
</button>
<navid="main-menu"hidden>
<ahref="/about">About</a>
</nav>
If the controlled element does not exist yet or the ID is not available, remove the attribute:
<buttonaria-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.
<ahref="#"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.
<buttontype="button"aria-controls="">Open menu</button>
Fixed: provide a valid id reference
Point aria-controls to the id of the element being controlled.
<buttontype="button"aria-controls="details-panel"aria-expanded="false">
Toggle details
</button>
<divid="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.
<buttontype="button"aria-controls="filters results">
Show filters and results
</button>
<sectionid="filters"hidden>
<p>Filter options...</p>
</section>
<sectionid="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.
<ahref="/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>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>aria-controls Toggle Example</title>
</head>
<body>
<buttontype="button"aria-controls="info-panel"aria-expanded="false">
Toggle info
</button>
<divid="info-panel"hidden>
<p>Extra information goes here.</p>
</div>
<script>
constbtn=document.querySelector('button');
constpanel=document.getElementById('info-panel');
btn.addEventListener('click',()=>{
constexpanded=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 -->
<buttontype="button"aria-controls="sidebar-nav"aria-expanded="false">
Menu
</button>
<navid="sidebar-nav"hidden>
<ul>
<li><ahref="/">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.
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
menuitemelement is a DOM descendant of themenuormenubarelement. - The
aria-ownsattribute — themenuormenubarelement usesaria-ownsto reference themenuitemby itsid, 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"orrole="menubar", either through DOM nesting or viaaria-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.
- Use
- 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>
<divrole="menuitem">Cut</div>
<divrole="menuitem">Copy</div>
<divrole="menuitem">Paste</div>
</div>
Correct — menuitem inside a menu
Wrapping the items in an element with role="menu" resolves the issue:
<divrole="menu">
<divrole="menuitem"tabindex="0">Cut</div>
<divrole="menuitem"tabindex="-1">Copy</div>
<divrole="menuitem"tabindex="-1">Paste</div>
</div>
Correct — menuitem inside a menubar
For a persistent horizontal menu bar with application-style actions:
<divrole="menubar">
<divrole="menuitem"tabindex="0">File</div>
<divrole="menuitem"tabindex="-1">Edit</div>
<divrole="menuitem"tabindex="-1">View</div>
</div>
Correct — nested menus with dropdown submenus
A menubar with a dropdown menu containing additional menuitem elements:
<divrole="menubar">
<divrole="menuitem"tabindex="0"aria-haspopup="true"aria-expanded="false">
File
<divrole="menu">
<divrole="menuitem"tabindex="-1">New</div>
<divrole="menuitem"tabindex="-1">Open</div>
<divrole="menuitem"tabindex="-1">Save</div>
</div>
</div>
<divrole="menuitem"tabindex="-1"aria-haspopup="true"aria-expanded="false">
Edit
<divrole="menu">
<divrole="menuitem"tabindex="-1">Cut</div>
<divrole="menuitem"tabindex="-1">Copy</div>
<divrole="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:
<divrole="menu"aria-owns="item-cut item-copy item-paste"></div>
<divrole="menuitem"id="item-cut"tabindex="0">Cut</div>
<divrole="menuitem"id="item-copy"tabindex="-1">Copy</div>
<divrole="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:
<navaria-label="Main navigation">
<ul>
<li><ahref="/">Home</a></li>
<li><ahref="/about">About</a></li>
<li><ahref="/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 aria-describedby attribute cannot be an empty string. It must contain at least one valid ID reference, or be removed entirely.
The aria-describedby attribute accepts a space-separated list of id values that point to elements providing a description for the current element. When a screen reader encounters an element with aria-describedby, it reads the text content of each referenced element as additional context.
An empty string ("") is not a valid IDREF value. The W3C validator expects at least one non-whitespace character. This often happens when a template engine or JavaScript outputs an empty variable into the attribute, or when a CMS generates the attribute without a corresponding value.
If no description exists, remove the attribute altogether. An absent aria-describedby is perfectly fine — it simply means the element has no supplementary description. Assistive technologies handle missing attributes gracefully, but an empty one can cause unpredictable behavior.
Examples
Invalid: empty aria-describedby
<ahref="/settings"aria-describedby="">Account settings</a>
Fixed: attribute removed
<ahref="/settings">Account settings</a>
Fixed: attribute references a valid ID
<ahref="/settings"aria-describedby="settings-note">Account settings</a>
<pid="settings-note">Opens your profile and notification preferences.</p>
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
forattribute references anidthat doesn't exist in the document. - The
forattribute references an element that isn't a labelable element (such as a<div>or<span>). - The
forattribute references an<input type="hidden">, which is not a visible form control and cannot be labeled. - There's a typo or mismatch between the
forvalue and the intended element'sid.
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
<labelfor="username">Username</label>
<inputid="user-name"type="text">
The for value "username" doesn't match the input's id of "user-name".
✅ Fixed: matching for and id
<labelfor="username">Username</label>
<inputid="username"type="text">
❌ for references a hidden input
<labelfor="token">Token</label>
<inputid="token"type="hidden"value="abc123">
An <input type="hidden"> is not a visible form control and cannot be labeled.
✅ Fixed: remove the unnecessary label
<inputid="token"type="hidden"value="abc123">
Hidden inputs don't need labels since users never interact with them directly.
❌ for references a non-labelable element
<labelfor="info">Information</label>
<divid="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
<labelfor="info">Information</label>
<textareaid="info"></textarea>
❌ for references an element inside another form control
<labelfor="opt">Choose one</label>
<select>
<optionid="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
<labelfor="choice">Choose one</label>
<selectid="choice">
<optionvalue="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
<inputtype="number">
</label>
This approach avoids the for/id mismatch problem entirely and is equally valid for accessibility.
The aria-describedby attribute cannot be an empty string — it must either contain valid ID references or be removed entirely.
The aria-describedby attribute accepts one or more ID values (separated by spaces) that point to elements providing additional descriptive text for the current element. When a screen reader focuses on the element, it reads the content of the referenced elements to give the user more context.
Setting aria-describedby="" is invalid because the attribute expects at least one valid IDREF — a non-empty string that matches the id of another element in the page. An empty value doesn't reference anything and creates a validation error. If no description is needed, simply omit the attribute altogether.
This commonly happens when a template or JavaScript dynamically sets the attribute but provides an empty fallback value instead of removing the attribute entirely.
Invalid Example
<labelfor="email">Email</label>
<inputtype="email"id="email"aria-describedby="">
Fixed Examples
If there is no description to reference, remove the attribute:
<labelfor="email">Email</label>
<inputtype="email"id="email">
If a description exists, point to its id:
<labelfor="email">Email</label>
<inputtype="email"id="email"aria-describedby="email-hint">
<pid="email-hint">We'll never share your email with anyone.</p>
If you're generating the attribute dynamically, make sure your code removes aria-describedby entirely rather than setting it to an empty string when no hint is available.
The aria-controls attribute must contain at least one valid ID reference and cannot be an empty string.
The aria-controls attribute accepts a space-separated list of IDs pointing to elements that are controlled by the current element. When a screen reader encounters aria-controls, it can offer navigation to those referenced elements. An empty string is not a valid IDREF value per the WAI-ARIA specification, so the W3C validator rejects it.
If the element does not currently control anything, remove the aria-controls attribute entirely rather than leaving it empty. Setting it to an empty string does not mean "no value" — it means "invalid value," which can confuse assistive technologies.
If the controlled element is added dynamically (for example, a dropdown that appears after user interaction), add aria-controls through JavaScript at the same time the controlled element appears in the DOM, and remove it when the element is gone.
Examples
Invalid: empty aria-controls
<divaria-controls=""aria-expanded="false">
Toggle panel
</div>
Fixed: remove the attribute when unused
<divaria-expanded="false">
Toggle panel
</div>
Fixed: provide a valid ID when a controlled element exists
<divaria-controls="panel-1"aria-expanded="true">
Toggle panel
</div>
<divid="panel-1">
Panel content here.
</div>
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
sizeswheneversrcsetuses width descriptors. Omitting it produces invalid HTML. - Correct image selection: Without
sizes, browsers fall back to assuming the image will be100vwwide, 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
sizesattribute 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(orsource) element that uses width descriptors insrcset. - 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
sizesattribute that describes those widths using media conditions and CSS length values likepx,vw,em, orcalc()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 declareutf-8as 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
<metahttp-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)
<metacharset="utf-8">
Correct: Using the http-equiv pragma directive with utf-8
<metahttp-equiv="Content-Type"content="text/html; charset=utf-8">
Full document example
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="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.
The label element associates a caption with a form control. There are two ways to create this association:
- Implicit association — Place the form control directly inside the
labelelement. Nofororidattributes are needed. - Explicit association — Use the
forattribute on thelabel, setting its value to theidof the associated form control.
Both methods work independently. The problem arises when you mix them incorrectly — nesting a select inside a label that has a for attribute, but the select either has no id or has an id that doesn't match the for value. In this situation, the explicit association (via for) points to nothing or to the wrong element, while the implicit association (via nesting) still exists. This contradictory state violates the HTML specification and can cause assistive technologies like screen readers to misidentify or skip the label, reducing accessibility for users who rely on them.
The WHATWG HTML specification requires that when a for attribute is present on a label, it must reference a valid labelable element by id. If the form control is already nested inside the label, the for attribute is redundant — but if present, it still must correctly reference that control.
How to Fix
You have two options:
- Remove the
forattribute — If theselectis already inside thelabel, implicit association handles everything. This is the simplest fix. - Add or correct the
id— Keep theforattribute but ensure theselecthas a matchingid.
Examples
❌ Incorrect: for attribute with no matching id
The for attribute references "age", but the select has no id at all:
<labelfor="age">
Age
<select>
<option>Young</option>
<option>Old</option>
</select>
</label>
❌ Incorrect: for attribute with a mismatched id
The for attribute references "age", but the select has a different id:
<labelfor="age">
Age
<selectid="age-select">
<option>Young</option>
<option>Old</option>
</select>
</label>
✅ Fix option 1: Remove the for attribute (implicit association)
Since the select is nested inside the label, implicit association is sufficient:
<label>
Age
<select>
<option>Young</option>
<option>Old</option>
</select>
</label>
✅ Fix option 2: Match the for attribute with the id (explicit association)
Keep the for attribute and give the select a matching id:
<labelfor="age">
Age
<selectid="age">
<option>Young</option>
<option>Old</option>
</select>
</label>
✅ Fix option 3: Separate the label and select (explicit association only)
If you prefer to keep the select outside the label, explicit association with matching for and id is required:
<labelfor="age">Age</label>
<selectid="age">
<option>Young</option>
<option>Old</option>
</select>
In most cases, option 1 (removing the for attribute) is the cleanest solution when the control is already nested. Use explicit association when the label and control are in separate parts of the DOM, such as in complex table or grid layouts.
The meta element's charset declaration must specify utf-8 when using HTML5. Values like iso-8859-15 are not valid in the HTML living standard.
In HTML5, the only permitted character encoding declared via a meta element is UTF-8. This applies to both the shorthand form (<meta charset="utf-8">) and the longer http-equiv form (<meta http-equiv="Content-Type" content="text/html; charset=utf-8">). Older encodings like iso-8859-15, windows-1252, or iso-8859-1 are not allowed.
The WHATWG HTML standard requires documents to be encoded in UTF-8. If a document uses a legacy encoding, the validator rejects it because the specification explicitly states that the charset value must be utf-8 (case-insensitive).
The shorthand <meta charset="utf-8"> is the preferred form in HTML5. It is shorter and does exactly the same thing as the http-equiv variant. There is no need to use both.
When switching from a legacy encoding, make sure the file itself is actually saved as UTF-8. Declaring UTF-8 in the markup while the file is saved in a different encoding will cause garbled characters. Most modern text editors let you choose the encoding when saving.
Examples
Invalid
<metahttp-equiv="Content-Type"content="text/html; charset=iso-8859-15">
Valid
<metacharset="utf-8">
The as attribute specifies the type of content a <link> element is fetching — such as "style", "script", "font", or "image". The browser uses this information to set the correct request headers, apply the right Content Security Policy, and assign the appropriate priority to the request. However, the HTML specification restricts the as attribute to <link> elements whose rel attribute is either "preload" or "modulepreload". Using as with any other rel value (like "stylesheet", "icon", or a missing rel altogether) is invalid HTML.
This validation error commonly occurs in two scenarios:
- You intended to preload a resource but forgot to set
rel="preload"— for example, writing<link href="styles.css" as="style">without specifyingrel. - You added
asto a regular<link>by mistake — for example, writing<link rel="stylesheet" href="styles.css" as="style">, whereasis unnecessary becauserel="stylesheet"already tells the browser what type of resource it is.
Getting this right matters for several reasons. Browsers rely on valid rel values to determine how to handle linked resources. An incorrect combination may cause the browser to ignore the as attribute entirely, leading to double-fetching of resources or incorrect prioritization. Additionally, invalid HTML can cause unpredictable behavior across different browsers.
How to fix it
- If you want to preload a resource (font, stylesheet, image, script, etc.), set
rel="preload"and keep theasattribute. - If you want to preload a JavaScript module, set
rel="modulepreload". Theasattribute defaults to"script"for module preloads and is optional in that case. - If you're using a different
relvalue like"stylesheet"or"icon", remove theasattribute — it's not needed and not allowed.
Examples
Incorrect: as attribute without rel="preload"
This <link> has as="style" but no rel attribute:
<linkhref="styles.css"as="style">
Incorrect: as attribute with rel="stylesheet"
The as attribute is not valid on a stylesheet link:
<linkrel="stylesheet"href="styles.css"as="style">
Correct: preloading a stylesheet
Use rel="preload" with the as attribute to hint the resource type:
<linkrel="preload"href="styles.css"as="style">
Note that preloading a stylesheet doesn't apply it — you still need a separate <link rel="stylesheet"> to actually use the CSS.
Correct: preloading a font
<linkrel="preload"href="font.woff2"as="font"type="font/woff2"crossorigin>
The crossorigin attribute is required when preloading fonts, even if they're served from the same origin.
Correct: preloading a JavaScript module
<linkrel="modulepreload"href="app.js">
With rel="modulepreload", the as attribute defaults to "script", so you can omit it. You may still include it explicitly if you prefer:
<linkrel="modulepreload"href="app.js"as="script">
Correct: regular stylesheet (no as needed)
If you simply want to load a stylesheet, no as attribute is required:
<linkrel="stylesheet"href="styles.css">
Full document example
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="UTF-8">
<metaname="viewport"content="width=device-width, initial-scale=1.0">
<title>Preload Example</title>
<!-- Preload critical resources -->
<linkrel="preload"href="styles.css"as="style">
<linkrel="preload"href="hero.jpg"as="image">
<linkrel="preload"href="font.woff2"as="font"type="font/woff2"crossorigin>
<!-- Apply the stylesheet -->
<linkrel="stylesheet"href="styles.css">
</head>
<body>
<h1>Hello, World!</h1>
<imgsrc="hero.jpg"alt="Hero banner">
</body>
</html>
The label element represents a caption for a form control. There are two ways to associate a label with its control:
- Implicit association — Place the form control directly inside the
labelelement. Noforattribute is needed because the browser automatically pairs the label with the nested control. - Explicit association — Use the
forattribute on thelabel, setting its value to theidof the target form control. The control doesn't need to be nested inside thelabelin this case.
Both methods are valid on their own. The problem occurs when you combine them incorrectly: you nest an input inside a label that has a for attribute, but the input either has no id or has an id that doesn't match the for value. This creates a contradiction — the for attribute points to a specific id, yet the nested input doesn't fulfill that reference. Browsers may handle this inconsistently, and assistive technologies like screen readers could fail to announce the label correctly, harming accessibility.
Why this matters
- Accessibility: Screen readers rely on the
for/idpairing to announce labels for form controls. A mismatched or missingidcan leave the control unlabeled for users who depend on assistive technology. - Standards compliance: The HTML specification requires that when a
forattribute is present, it must reference theidof a labelable element. A mismatch violates this rule. - Browser behavior: Some browsers will fall back to the implicit association when
fordoesn't resolve, but others may prioritize the broken explicit association, leaving the control effectively unlabeled.
How to fix it
You have two options:
- Remove the
forattribute if theinputis already nested inside thelabel. The implicit association is sufficient on its own. - Add or correct the
idon the nestedinputso it matches theforattribute value exactly.
Examples
❌ Nested input with no matching id
The for attribute says "email", but the input has no id at all:
<labelfor="email">
<inputtype="email"name="email">
</label>
❌ Nested input with a mismatched id
The for attribute says "email", but the input's id is "user-email":
<labelfor="email">
<inputtype="email"name="email"id="user-email">
</label>
✅ Fix by removing the for attribute (implicit association)
Since the input is nested inside the label, the association is automatic:
<label>
<inputtype="email"name="email">
</label>
✅ Fix by adding a matching id (explicit association)
The for value and the id value are identical:
<labelfor="email">
<inputtype="email"name="email"id="email">
</label>
✅ Fix by using explicit association without nesting
If you prefer to keep the for attribute, the input doesn't need to be nested at all:
<labelfor="email">Email</label>
<inputtype="email"name="email"id="email">
In most cases, choosing either implicit or explicit association — rather than mixing both — is the simplest way to avoid this error. If you do combine them, just make sure the for value and the id value match exactly.
The core problem is that aria-disabled="true" is purely an accessibility hint — it communicates a disabled state to assistive technologies like screen readers, but it has no effect on the actual behavior of the element. When an a element has an href attribute, the browser treats it as a valid hyperlink regardless of any ARIA attributes. Users can still click it, follow it via keyboard navigation, and navigate to its destination. This mismatch between the announced state ("disabled") and actual behavior ("fully functional link") creates a confusing and misleading experience, particularly for users of assistive technologies.
The W3C validator flags this combination because it violates the principle that ARIA states should accurately reflect an element's true interactive state. A link that claims to be disabled but still works undermines user trust and can cause real usability problems.
Why this matters
- Accessibility: Screen readers will announce the link as disabled, but users who activate it will be unexpectedly navigated away. This is disorienting and violates WCAG guidance on predictable behavior.
- Standards compliance: The HTML specification and ARIA in HTML requirements discourage or disallow this combination because it produces an unreliable user experience.
- Browser behavior: No browser will disable a link just because
aria-disabled="true"is present. Thehrefattribute always makes theaelement an active hyperlink.
How to fix it
You have two main approaches depending on your intent:
The link should be active: Remove
aria-disabled="true"and keep thehref. If the link works, don't mark it as disabled.The link should be disabled: Remove the
hrefattribute. Withouthref, theaelement becomes a placeholder link that is not interactive. You can then usearia-disabled="true"to communicate the disabled state,tabindex="-1"to remove it from the keyboard tab order, and CSS to style it as visually disabled. You should also add JavaScript to prevent activation if needed.
Examples
Incorrect
This triggers the validation error because aria-disabled="true" conflicts with the presence of href:
<ahref="/dashboard"aria-disabled="true">Go to Dashboard</a>
Correct — Keep the link active
If the link should function normally, simply remove the aria-disabled attribute:
<ahref="/dashboard">Go to Dashboard</a>
Correct — Disable the link
If the link should be non-actionable (e.g., a navigation item the user doesn't currently have access to), remove the href attribute and use ARIA and CSS to communicate the disabled state:
<aaria-disabled="true"tabindex="-1"role="link"class="link-disabled">Go to Dashboard</a>
.link-disabled{
color:#6c757d;
cursor: not-allowed;
pointer-events: none;
text-decoration: none;
}
In this approach:
- Removing
hrefensures the link is not actionable by the browser. aria-disabled="true"tells assistive technologies the element is disabled.tabindex="-1"removes the element from the keyboard tab order so users can't Tab to it.role="link"preserves the link semantics so screen readers still identify it as a link (anawithouthrefloses its implicit link role).- The CSS provides a visual indication that the element is disabled, with
pointer-events: nonepreventing mouse clicks andcursor: not-allowedgiving a visual cue on hover.
Correct — Use a button instead
If the "link" triggers an action rather than navigating somewhere, consider using a button element instead. Buttons natively support the disabled attribute:
<buttontype="button"disabled>Perform Action</button>
This is the simplest and most robust solution when the element doesn't need to be a link. The disabled attribute is natively understood by browsers and assistive technologies without any ARIA workarounds.
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