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 validator error occurs when an element such as an a, button, or custom widget includes aria-controls="" (empty) or whitespace-only. The aria-controls attribute takes one or more space-separated id values (IDREFS). Each referenced id must exist exactly once in the same document. Leaving it empty violates the ARIA and HTML requirements and provides no usable relationship for assistive technologies.
Why this matters:
- Accessibility: Screen readers rely on aria-controls to announce relationships between controls and controlled regions (e.g., a toggle and its panel). An empty value misleads AT or adds noise.
- Standards compliance: HTML and ARIA require at least one non-whitespace id. Empty values cause validation failures.
- Robustness: Incorrect references can confuse scripts and future maintainers, and break behavior when IDs change.
How to fix it:
- Only add aria-controls when the element truly controls another region (show/hide, sort, update).
- Ensure the controlled element has a unique id.
- Set aria-controls to that id (or multiple space-separated IDs).
- Keep the reference in sync if IDs change.
- If nothing is controlled, remove aria-controls entirely.
Examples
Invalid: empty aria-controls (triggers the error)
<a href="#" aria-controls="">Toggle details</a>
Valid: control a single region
<div id="details-panel" hidden>
Some details...
</div>
<a href="#details-panel" aria-controls="details-panel">Toggle details</a>
Valid: control multiple regions (space-separated IDs)
<section id="filters" hidden>...</section>
<section id="results" hidden>...</section>
<button type="button" aria-controls="filters results">Show filters and results</button>
Valid: remove when not needed
<a href="#">Toggle details</a>
Minimal complete document with proper usage
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>aria-controls Example</title>
</head>
<body>
<button type="button" aria-controls="info" aria-expanded="false">Toggle info</button>
<div id="info" hidden>
Extra information.
</div>
<script>
const btn = document.querySelector('button');
const panel = document.getElementById(btn.getAttribute('aria-controls'));
btn.addEventListener('click', () => {
const expanded = btn.getAttribute('aria-expanded') === 'true';
btn.setAttribute('aria-expanded', String(!expanded));
panel.hidden = expanded;
});
</script>
</body>
</html>
Tips:
- Use aria-controls for functional relationships (control affects content), not just visual proximity.
- Combine with aria-expanded when toggling visibility to convey state.
- Verify that every id in aria-controls exists and is unique; avoid dynamic mismatches created by templating or component reuse.
The 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
<label for="email">Email</label>
<input type="email" id="email" aria-describedby="">
Fixed Examples
If there is no description to reference, remove the attribute:
<label for="email">Email</label>
<input type="email" id="email">
If a description exists, point to its id:
<label for="email">Email</label>
<input type="email" id="email" aria-describedby="email-hint">
<p id="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-labelledby attribute accepts an IDREFS value — a space-separated list of one or more id values that reference other elements in the document. The validator expects each ID in the list to be non-empty and contain at least one non-whitespace character. When the attribute is set to an empty string (aria-labelledby=""), it violates this constraint and triggers the validation error.
This issue commonly arises in templating systems and JavaScript frameworks where a variable intended to hold an ID reference resolves to an empty string. For example, a template like aria-labelledby="{{ labelId }}" will produce an empty attribute if labelId is undefined or blank.
Why this matters
The aria-labelledby attribute is one of the highest-priority methods for computing an element’s accessible name. According to the accessible name computation algorithm, aria-labelledby overrides all other naming sources — including visible text content, aria-label, and the title attribute. When aria-labelledby is present but empty or broken, screen readers may calculate the link’s accessible name as empty, effectively making the link invisible or meaningless to assistive technology users. A link with no accessible name is a significant accessibility barrier: users cannot determine where the link goes or what it does.
Beyond accessibility, an empty aria-labelledby also signals invalid HTML according to both the WHATWG HTML living standard and the WAI-ARIA specification, which define the IDREFS type as requiring at least one valid token.
How to fix it
You have several options depending on your situation:
- Reference a valid ID — Point aria-labelledby to the id of an existing element whose text content should serve as the link’s accessible name.
- Remove the attribute and use visible link text — If the link already contains descriptive text, aria-labelledby is unnecessary.
- Use aria-label instead — For icon-only links where no visible label element exists, aria-label provides a concise accessible name directly on the element.
- Conditionally render the attribute — In templates, use conditional logic to omit aria-labelledby entirely when there’s no valid ID to reference, rather than rendering an empty value.
Examples
Invalid: empty aria-labelledby
This triggers the validation error because the attribute value contains no non-whitespace characters.
<a href="/report" aria-labelledby=""></a>
Invalid: whitespace-only aria-labelledby
A value containing only spaces is equally invalid — IDREFS requires at least one actual token.
<a href="/report" aria-labelledby=" "></a>
Fixed: referencing an existing element by id
The aria-labelledby attribute points to a <span> whose text content becomes the link’s accessible name.
<a href="/report" aria-labelledby="report-link-text">
<svg aria-hidden="true" viewBox="0 0 16 16"></svg>
</a>
<span id="report-link-text">View report</span>
Fixed: referencing multiple IDs
You can concatenate text from multiple elements by listing their IDs separated by spaces. The accessible name is built by joining the referenced text in order.
<span id="action">Learn more:</span>
<span id="subject">Apples</span>
<a href="/apples" aria-labelledby="action subject">
<svg aria-hidden="true" viewBox="0 0 16 16"></svg>
</a>
In this case, the computed accessible name is “Learn more: Apples”.
Fixed: using visible link text instead
When the link already contains descriptive text, no ARIA attribute is needed. This is the simplest and most robust approach.
<a href="/report">View report</a>
Fixed: using aria-label for an icon-only link
When there is no separate visible label element to reference, aria-label provides the accessible name directly.
<a href="/search" aria-label="Search">
<svg aria-hidden="true" viewBox="0 0 16 16"></svg>
</a>
Fixed: conditional rendering in a template
If you’re using a templating engine, conditionally include the attribute only when a value exists. The exact syntax varies by framework, but here’s the general idea:
<!-- Instead of always rendering the attribute: -->
<!-- <a href="/report" aria-labelledby="{{ labelId }}"> -->
<!-- Only render it when labelId has a value: -->
<!-- <a href="/report" {{#if labelId}}aria-labelledby="{{labelId}}"{{/if}}> -->
This prevents the empty-attribute problem at its source rather than patching it after the fact.
The aria-labelledby attribute is an IDREFS attribute, meaning its value must be a space-separated list of one or more id values that exist in the document. These referenced elements collectively provide the accessible name for the element. When the value is an empty string ("") or contains only whitespace, there are no valid ID references, which violates the IDREFS requirement defined in the WAI-ARIA and HTML specifications.
This issue commonly appears when templating systems or JavaScript frameworks conditionally set aria-labelledby but output an empty string when no label ID is available. It also occurs when developers add the attribute as a placeholder with the intention of filling it in later but forget to do so.
Why this matters
An empty aria-labelledby is problematic for several reasons:
- Accessibility: Screen readers rely on aria-labelledby to announce the accessible name of an element. An empty value can cause unpredictable behavior — some screen readers may ignore the SVG entirely, while others may fall back to reading unhelpful content or nothing at all. This leaves users who depend on assistive technology without a meaningful description of the graphic.
- Standards compliance: The W3C validator flags this as an error because the HTML specification requires IDREFS attributes to contain at least one non-whitespace character. Shipping invalid HTML can signal broader quality issues and may cause problems in strict parsing environments.
- Maintainability: An empty aria-labelledby is ambiguous. It’s unclear whether the developer intended the SVG to be decorative, forgot to add a reference, or encountered a bug in their templating logic.
How to fix it
Choose the approach that matches your intent:
- Reference a labeling element by ID: If the SVG conveys meaning, add a <title> element (or another visible text element) inside or near the SVG with a unique id, then set aria-labelledby to that id. IDs are case-sensitive, so ensure an exact match.
- Use aria-label instead: If you want to provide an accessible name directly as a text string without needing a separate element, replace aria-labelledby with aria-label.
- Remove the attribute: If the SVG already has an accessible name through other means (such as visible adjacent text or a <title> child that doesn’t need explicit referencing), simply remove the empty aria-labelledby.
- Mark as decorative: If the SVG is purely decorative and adds no information, remove aria-labelledby and add aria-hidden="true" so assistive technology skips it entirely.
When generating aria-labelledby dynamically, ensure your code omits the attribute entirely rather than outputting an empty value when no label ID is available.
Examples
❌ Empty aria-labelledby (triggers the error)
<svg role="img" aria-labelledby="">
<use href="#icon-star"></use>
</svg>
The empty string is not a valid IDREFS value, so the validator reports an error.
✅ Reference a <title> element by ID
<svg role="img" aria-labelledby="star-title">
<title id="star-title">Favorite</title>
<use href="#icon-star"></use>
</svg>
The aria-labelledby points to the <title> element’s id, giving the SVG a clear accessible name of “Favorite.”
✅ Use aria-label with a text string
<svg role="img" aria-label="Favorite">
<use href="#icon-star"></use>
</svg>
When you don’t need to reference another element, aria-label provides the accessible name directly as an attribute value.
✅ Reference multiple labeling elements
<svg role="img" aria-labelledby="star-title star-desc">
<title id="star-title">Favorite</title>
<desc id="star-desc">A five-pointed star icon</desc>
<use href="#icon-star"></use>
</svg>
The aria-labelledby value can include multiple space-separated IDs. The accessible name is constructed by concatenating the text content of the referenced elements in order.
✅ Decorative SVG (no accessible name needed)
<svg aria-hidden="true" focusable="false">
<use href="#icon-decorative-divider"></use>
</svg>
For purely decorative graphics, aria-hidden="true" removes the element from the accessibility tree. Adding focusable="false" prevents the SVG from receiving keyboard focus in older versions of Internet Explorer and Edge.
The autocomplete attribute tells the browser whether and how it should autofill a form field. The HTML living standard defines a specific set of allowed values — an empty string is not among them. When the attribute is present but empty, the browser receives ambiguous instructions: you’ve explicitly declared the attribute, signaling intent, but provided no actual directive. Different browsers may interpret this inconsistently, some treating it as on, others ignoring it, and others falling back to default behavior.
This matters for several reasons:
- Standards compliance: The WHATWG HTML specification requires autocomplete to contain either the keywords on or off, or one or more valid autofill detail tokens. An empty string satisfies none of these.
- Accessibility: Autofill helps users with motor impairments or cognitive disabilities complete forms more quickly and accurately. An ambiguous autocomplete value can interfere with assistive technologies that rely on these hints.
- User experience: Specific autofill tokens like email, tel, street-address, and current-password allow browsers and password managers to suggest the right data for the right field. Using them correctly makes forms faster and easier to complete.
This issue commonly arises when a framework or template engine outputs autocomplete="" as a default, or when a developer intends to disable autocomplete but leaves the value blank instead of using off.
How to fix it
Choose one of these approaches depending on your intent:
- Remove the attribute if you want default browser behavior (the browser decides whether to autofill).
- Use on to explicitly allow autofill.
- Use off to explicitly discourage autofill (note: browsers may still autofill for login fields regardless).
- Use a specific autofill token to tell the browser exactly what kind of data the field expects. This is the most helpful option for users.
Common autofill tokens include: name, given-name, family-name, email, username, new-password, current-password, tel, street-address, postal-code, country, and cc-number. You can find the full list in the WHATWG autofill specification.
Examples
Incorrect: empty autocomplete value
<input type="text" name="username" autocomplete="">
This triggers the validation error because the attribute is present but contains no valid token.
Correct: remove the attribute entirely
If you have no specific autofill preference, simply omit the attribute:
<input type="text" name="username">
Correct: use on or off
Explicitly enable or disable autofill:
<input type="text" name="username" autocomplete="on">
<input type="text" name="search-query" autocomplete="off">
Correct: use specific autofill tokens
Specific tokens give browsers the best hints for filling in the right data. This is the recommended approach for forms that collect personal information:
<form>
<label for="name">Full name</label>
<input type="text" id="name" name="name" autocomplete="name">
<label for="useremail">Email</label>
<input type="email" id="useremail" name="useremail" autocomplete="email">
<label for="phone">Phone</label>
<input type="tel" id="phone" name="phone" autocomplete="tel">
<label for="pwd">Password</label>
<input type="password" id="pwd" name="pwd" autocomplete="current-password">
<button type="submit">Sign in</button>
</form>
Using precise tokens like current-password and email helps password managers and mobile keyboards provide the most relevant suggestions, improving the experience for all users.
The for attribute on a <label> element tells the browser which form control the label describes. When a user clicks or taps the label, the browser transfers focus to the associated control. For this to work, the value of for must exactly match the id of a form element such as an <input>, <textarea>, <select>, or <button>.
An empty string ("") is not a valid ID according to the HTML specification. The WHATWG HTML standard requires that an id attribute value must contain at least one character and must not contain ASCII whitespace. Since no element can have an empty-string id, a <label> with for="" can never successfully reference anything, making it both invalid markup and a broken association.
Why this matters
Accessibility: Screen readers rely on the for/id pairing to announce what a form control is for. A label with an empty for attribute creates no programmatic association, meaning assistive technology users may not know what a field is asking for. This directly impacts WCAG compliance.
Usability: A properly associated label expands the clickable area of its form control. For example, clicking a label associated with a checkbox will toggle the checkbox. An empty for attribute breaks this behavior.
Standards compliance: The W3C validator flags this because it violates the HTML specification. Keeping markup valid helps ensure consistent behavior across browsers and future-proofs your code.
How to fix
You have three options:
- Set for to a valid id: Give the associated form control a unique id and reference it in the label’s for attribute.
- Remove for and use implicit association: Wrap the form control inside the <label> element. This creates an implicit association without needing for or id at all.
- Remove the for attribute: If the label is purely decorative or not meant to be associated with a control, simply remove the empty for attribute.
Examples
❌ Empty for attribute (triggers the error)
<label for="">Username:</label>
<input type="text" name="username">
The label has no meaningful association with the input because for="" is not a valid reference.
✅ Fix: Use a valid for/id pair
<label for="username">Username:</label>
<input type="text" id="username" name="username">
The for="username" now matches id="username" on the input, creating an explicit association.
✅ Fix: Use implicit association by nesting
<label>
Username:
<input type="text" name="username">
</label>
Wrapping the input inside the <label> creates an implicit association. No for or id attributes are needed.
❌ Multiple labels with empty for attributes
<form>
<label for="">Email:</label>
<input type="email" name="email">
<label for="">Subscribe to newsletter</label>
<input type="checkbox" name="subscribe">
</form>
✅ Fixed with proper associations
<form>
<label for="email">Email:</label>
<input type="email" id="email" name="email">
<label for="subscribe">Subscribe to newsletter</label>
<input type="checkbox" id="subscribe" name="subscribe">
</form>
Each id must be unique within the document, and each for attribute must reference exactly one id. If your labels are generated by a framework or CMS with empty for values, check the template or component configuration to ensure proper id values are being output.
In a URL, the # character has a special role: it acts as the delimiter that separates the main URL from the fragment identifier. The fragment typically points to a specific section or element within the target document, often corresponding to an element’s id attribute. Because # serves this reserved purpose, it cannot appear more than once in its raw form within a URL. When the validator encounters something like ##pricing or section#one#two, it flags the extra # characters as illegal.
This issue usually arises from one of these common scenarios:
- Typos — accidentally typing ## instead of #.
- String concatenation bugs — building URLs programmatically where a # is included both in the base URL and prepended to the fragment value.
- Copy-paste errors — duplicating the # when copying URLs from browser address bars or other sources.
- Literal # intended in fragment — if you genuinely need a # symbol within the fragment text, it must be percent-encoded as %23.
This matters because browsers may handle malformed URLs inconsistently. Some browsers silently strip the extra #, while others may fail to navigate to the intended fragment. Malformed URLs also cause problems for assistive technologies, web crawlers, and any tooling that parses links. Keeping your URLs well-formed ensures predictable behavior across all user agents and complies with the URL Standard and HTML specification.
Examples
Incorrect: duplicate # in the URL
The double ## makes the fragment identifier invalid:
<a href="https://example.com/faqs##pricing">Pricing</a>
Correct: single # delimiter
Remove the extra # so that pricing is the fragment:
<a href="https://example.com/faqs#pricing">Pricing</a>
Incorrect: extra # inside the fragment
Here, the fragment portion overview#details contains a raw #, which is not allowed:
<a href="/docs#overview#details">Details</a>
Correct: percent-encode the literal #
If you truly need a # as part of the fragment text, encode it as %23:
<a href="/docs#overview%23details">Details</a>
In most cases though, this pattern suggests the URL structure should be rethought. A cleaner approach is to link directly to the intended fragment:
<a href="/docs#details">Details</a>
Incorrect: programmatic concatenation error
A common bug in templates or JavaScript is prepending # when the variable already includes it:
<!-- If defined as defined as fragment = "#pricing", this produces a double ## -->
<a href="https://example.com/faqs#pricing">Pricing</a>
Correct: ensure only one # is present
Make sure either the base URL or the fragment variable includes the #, but not both:
<a href="https://example.com/faqs#pricing">Pricing</a>
Fragment-only links
Fragment-only links (links to sections within the same page) follow the same rule — only one #:
<!-- Incorrect -->
<a href="##contact">Contact Us</a>
<!-- Correct -->
<a href="#contact">Contact Us</a>
A URL fragment identifier is the part of a URL that follows the # character. It typically points to an element on the page that has a matching id attribute. According to the URL specification, certain characters — including spaces — are not allowed to appear literally in a URL. When the W3C HTML Validator encounters a raw space in a fragment, it reports this as an illegal character.
This issue matters for several reasons. Browsers may handle unescaped spaces in fragments inconsistently, leading to broken in-page navigation. Screen readers and other assistive technologies rely on well-formed URLs to navigate users to the correct section of a page. Additionally, spaces in id attributes are themselves invalid in HTML — the id attribute must not contain any ASCII whitespace characters. So the root cause often involves two separate violations: an invalid id and an invalid fragment URL.
The best approach is to use hyphens (-) or underscores (_) instead of spaces in your id values, then match the fragment accordingly. This produces clean, readable, and shareable URLs (e.g., page.html#contact-info instead of page.html#contact%20info). If you’re working with a CMS or build tool that auto-generates id values with spaces, configure it to produce hyphen-separated, lowercase identifiers instead.
If you absolutely cannot change the id values (e.g., they’re generated by a third-party system), you can percent-encode the spaces as %20 in the href. This satisfies URL syntax rules, but note that an id containing spaces is still invalid HTML on its own. Fixing the id is always the preferred solution.
Examples
Invalid: space in fragment and id
This triggers the validator error because href="#My Section" contains an unescaped space. The id="My Section" is also invalid HTML since id values cannot contain spaces.
<a href="#My Section">Go to section</a>
<h2 id="My Section">My Section</h2>
Fixed: hyphen-separated fragment and id
Replace spaces with hyphens in both the id and the fragment. This is the cleanest and most widely recommended approach.
<a href="#my-section">Go to section</a>
<h2 id="my-section">My Section</h2>
Fixed: underscore-separated fragment and id
Underscores work equally well if you prefer that convention.
<a href="#my_section">Go to section</a>
<h2 id="my_section">My Section</h2>
Alternative: percent-encoding the space
Encoding the space as %20 resolves the fragment URL error, but the id with a space is still invalid HTML. Use this only as a last resort when you cannot control the id values.
<a href="#My%20Section">Go to section</a>
<!-- Note: this id is still invalid HTML due to the space -->
<h2 id="My Section">My Section</h2>
Full valid document
A complete example demonstrating multiple in-page links with properly formatted fragments and id values:
<!doctype html>
<html lang="en">
<head>
<title>Page sections</title>
</head>
<body>
<nav>
<ul>
<li><a href="#getting-started">Getting Started</a></li>
<li><a href="#advanced-usage">Advanced Usage</a></li>
<li><a href="#frequently-asked-questions">FAQ</a></li>
</ul>
</nav>
<h2 id="getting-started">Getting Started</h2>
<p>Introduction content here.</p>
<h2 id="advanced-usage">Advanced Usage</h2>
<p>Advanced content here.</p>
<h2 id="frequently-asked-questions">Frequently Asked Questions</h2>
<p>FAQ content here.</p>
</body>
</html>
URLs follow strict syntax rules defined by RFC 3986. Only a specific set of characters are allowed directly in a URL path segment — these include letters, digits, hyphens (-), periods (.), underscores (_), and tildes (~), along with a handful of sub-delimiters like !, $, &, ', (, ), *, +, ,, ;, and =. Any character outside this set — including spaces, angle brackets (< >), curly braces ({ }), pipe characters (|), backslashes (\), carets (^), and backticks (`) — must be percent-encoded.
Percent-encoding replaces the character with a % sign followed by its two-digit hexadecimal ASCII code. For example:
| Character | Percent-encoded |
|---|---|
| (space) | %20 |
| { | %7B |
| } | %7D |
| | | %7C | | < | %3C | | > | %3E | | ^ | %5E |
This validation error matters for several reasons. First, browsers may handle illegal characters inconsistently — some may silently encode them, while others may break the link or navigate to an unexpected destination. Second, tools that parse HTML (screen readers, search engine crawlers, link checkers) rely on well-formed URLs and may fail or behave unpredictably when they encounter illegal characters. Third, standards compliance ensures your HTML works reliably across all environments.
Common causes of this error include:
- Copying and pasting URLs from documents or emails that contain unencoded spaces or special characters.
- Template variables or placeholders left in href values (e.g., {{url}}).
- File paths with spaces used directly as URLs without encoding.
- Non-ASCII characters in URLs that haven’t been properly encoded.
Examples
❌ Space in the URL path
<a href="/my page/about us.html">About Us</a>
✅ Spaces percent-encoded as %20
<a href="/my%20page/about%20us.html">About Us</a>
❌ Curly braces from a template placeholder left in the markup
<a href="/products/{{product-id}}/details">View Details</a>
✅ Curly braces replaced with an actual value
<a href="/products/42/details">View Details</a>
❌ Pipe character in the path
<a href="/search/color|size">Filter Results</a>
✅ Pipe character percent-encoded as %7C
<a href="/search/color%7Csize">Filter Results</a>
❌ Angle brackets in the URL
<a href="/page/<section>">Go to Section</a>
✅ Angle brackets percent-encoded
<a href="/page/%3Csection%3E">Go to Section</a>
How to Fix
- Identify the illegal character from the validator’s error message — it typically tells you exactly which character is problematic.
- Replace it with the correct percent-encoded equivalent using the table above or a URL encoder tool.
- If the URL contains template syntax (like {{...}}), make sure your templating engine processes it before the HTML is served to the browser. The raw template syntax should never appear in the final rendered HTML.
- Consider renaming files and directories to avoid spaces and special characters altogether — this is the cleanest long-term solution.
If you’re generating URLs programmatically, use built-in encoding functions like JavaScript’s encodeURIComponent() or PHP’s rawurlencode() to ensure all special characters are properly escaped before inserting them into href attributes.
The W3C HTML Validator checks that URLs used in attributes like href conform to the URL Standard maintained by WHATWG. According to this standard, only certain characters are permitted to appear literally in the query component of a URL. The pipe character (|, Unicode U+007C) is not in the set of allowed query characters, which means it must be percent-encoded as %7C when it appears in a URL’s query string.
While most modern browsers will silently handle a raw | in a URL and still navigate to the intended destination, relying on this behavior is problematic for several reasons:
- Standards compliance: HTML documents that contain unencoded special characters in URLs are technically invalid and will fail W3C validation.
- Interoperability: Not all user agents, HTTP clients, web scrapers, or proxy servers handle illegal URL characters the same way. An unencoded pipe could be misinterpreted, stripped, or cause unexpected behavior in certain environments.
- Security: Properly encoding URLs helps prevent injection attacks and ensures that each part of the URL is unambiguously parsed. Unencoded special characters can be exploited in certain contexts.
- Link sharing and processing: URLs are often copied, pasted, embedded in emails, or processed by APIs. An unencoded | may break the URL when it passes through systems that strictly enforce URL syntax.
This issue commonly arises when URLs are constructed by hand, pulled from databases, or generated by backend systems that don’t automatically encode query parameters. It can also appear when using pipe-delimited values as query parameter values (e.g., ?filter=red|blue|green).
The fix is straightforward: replace every literal | in the URL with its percent-encoded equivalent %7C. If you’re generating URLs in code, use built-in encoding functions like JavaScript’s encodeURIComponent() or PHP’s urlencode() to handle this automatically.
Examples
Incorrect: raw pipe character in query string
<a href="https://example.com/search?q=test|demo">Search</a>
The literal | in the query string triggers the validation error.
Correct: pipe character percent-encoded
<a href="https://example.com/search?q=test%7Cdemo">Search</a>
Replacing | with %7C makes the URL valid. The server receiving this request will decode it back to test|demo automatically.
Incorrect: multiple pipe characters as delimiters
<a href="https://example.com/filter?colors=red|blue|green">Filter colors</a>
Correct: all pipe characters encoded
<a href="https://example.com/filter?colors=red%7Cblue%7Cgreen">Filter colors</a>
Generating encoded URLs in JavaScript
If you’re building URLs dynamically, use encodeURIComponent() to encode individual parameter values:
<script>
const colors = "red|blue|green";
const url = "https://example.com/filter?colors=" + encodeURIComponent(colors);
// Result: "https://example.com/filter?colors=red%7Cblue%7Cgreen"
</script>
This ensures that any special characters in the value — including |, spaces, ampersands, and others — are properly encoded without you needing to remember each character’s percent-encoded form.
Other characters to watch for
The pipe character is not the only one that causes this validation error. Other characters that must be percent-encoded in URL query strings include curly braces ({ and }), the caret (^), backtick (`), and square brackets ([ and ]) when used outside of specific contexts. As a general rule, always encode user-supplied or dynamic values using your language’s URL encoding function rather than constructing query strings through simple string concatenation.
The href attribute expects a valid URL, and URLs follow strict syntax rules defined by RFC 3986. Under these rules, spaces are not permitted anywhere in a URL — not in the path, the query string, the fragment, or any other component. When a browser encounters a space in an href, it may attempt to fix the URL by encoding the space automatically, but this behavior is not guaranteed to be consistent across all browsers and contexts. Relying on browsers to silently correct invalid URLs is fragile and can lead to broken links.
This matters for several reasons. First, standards compliance: the W3C validator flags this because the HTML specification requires href values to be valid URLs. Second, interoperability: while most modern browsers handle spaces gracefully on navigation, other consumers of your HTML — such as web crawlers, screen readers, link checkers, and APIs that parse HTML — may not. Third, accessibility: assistive technologies rely on well-formed URLs to correctly announce and follow links. A malformed URL could lead to unexpected behavior for users depending on these tools.
The fix is straightforward: replace every literal space character with %20. This is called percent-encoding (sometimes called URL encoding). The sequence %20 is the hexadecimal representation of the space character (ASCII code 32). In the query string portion of a URL specifically, you may also see + used to represent spaces (as defined by the application/x-www-form-urlencoded format), but %20 is universally valid across all parts of a URL and is the safer choice.
Be aware that spaces can sometimes be hard to spot, especially trailing spaces or spaces introduced by template engines and CMS platforms that concatenate URL parts. If you’re generating URLs dynamically (e.g., in a server-side template or JavaScript), use built-in encoding functions like encodeURIComponent() in JavaScript or urlencode() in PHP rather than manually replacing spaces.
Examples
Incorrect: space in the query string
<a href="search.html?q=my search">Search for 'my search'</a>
The literal space between my and search makes this an invalid URL.
Correct: space replaced with %20
<a href="search.html?q=my%20search">Search for 'my search'</a>
Incorrect: spaces in the path
<a href="/files/my document.pdf">Download the document</a>
Spaces in the path segment are equally invalid.
Correct: spaces in the path encoded
<a href="/files/my%20document.pdf">Download the document</a>
Incorrect: multiple spaces across path and query
<a href="/product catalog/items?name=red shoes&category=on sale">Red Shoes</a>
Correct: all spaces encoded
<a href="/product%20catalog/items?name=red%20shoes&category=on%20sale">Red Shoes</a>
Note that in addition to encoding the spaces, the & in the query string should be written as & in HTML to avoid being interpreted as the start of an HTML entity.
Using JavaScript to encode URLs dynamically
If you’re building URLs in JavaScript, use encodeURIComponent() for individual parameter values or encodeURI() for full URLs:
<script>
const query = "my search";
const url = "search.html?q=" + encodeURIComponent(query);
// Result: "search.html?q=my%20search"
</script>
This approach prevents encoding issues by handling all special characters automatically — not just spaces, but also characters like #, &, =, and others that have special meaning in URLs.
The W3C HTML Validator checks that URLs in href attributes conform to the URL standard (defined by WHATWG). While square brackets are permitted in the host component of a URL (to support IPv6 addresses like [::1]), they are not valid unescaped characters in the query string — the part of the URL that comes after the ?. When the validator encounters a literal [ or ] in the query portion, it flags it as an illegal character.
This issue commonly arises when working with APIs or server-side frameworks that use square brackets in query parameters to represent arrays or nested data structures. For example, PHP-style query strings like ?filter[name]=foo or ?ids[]=1&ids[]=2 contain brackets that must be encoded for valid HTML.
Why this matters
- Standards compliance: The WHATWG URL Standard explicitly lists square brackets among the characters that must be percent-encoded in query strings. Invalid URLs cause W3C validation failures.
- Browser behavior: While most modern browsers are forgiving and will often handle unescaped brackets correctly, relying on this lenient parsing is fragile. Some HTTP clients, proxies, or intermediary servers may reject or mangle URLs with illegal characters.
- Interoperability: Encoded URLs are safer when copied, shared, or processed by tools like link checkers, web scrapers, or email clients that may perform strict URL parsing.
How to fix it
Replace every literal square bracket in the query string with its percent-encoded form:
| Character | Percent-encoded |
|---|---|
| [ | %5B |
| ] | %5D |
If you’re generating URLs dynamically in a server-side language or JavaScript, use the appropriate encoding function (e.g., encodeURIComponent() in JavaScript, urlencode() in PHP, or urllib.parse.quote() in Python) to handle this automatically.
Examples
Incorrect: literal brackets in the query string
<a href="https://example.com/search?filter[status]=active">Active items</a>
This triggers the validation error because [ and ] appear unescaped in the query.
Correct: percent-encoded brackets
<a href="https://example.com/search?filter%5Bstatus%5D=active">Active items</a>
Replacing [ with %5B and ] with %5D resolves the error. The server receiving this request will decode the values back to filter[status]=active.
Incorrect: array-style parameters with brackets
<a href="/api/items?ids[]=1&ids[]=2&ids[]=3">Load items</a>
Correct: array-style parameters encoded
<a href="/api/items?ids%5B%5D=1&ids%5B%5D=2&ids%5B%5D=3">Load items</a>
Note that in addition to encoding the brackets, the & characters in HTML attributes should be written as & for fully valid markup.
Incorrect: brackets in a simple value
<a href="search.html?q=[value]">Search</a>
Correct: encoded brackets in a simple value
<a href="search.html?q=%5Bvalue%5D">Search</a>
Note on brackets in the host (valid use)
Square brackets are valid in the host portion of a URL for IPv6 addresses. The following does not trigger the error:
<a href="http://[::1]:8080/page">IPv6 localhost</a>
The validator only flags brackets that appear in the query string or other parts of the URL where they are not permitted.
The W3C HTML Validator raises this error when it encounters a backslash character (\) inside the href attribute of an anchor element. According to the WHATWG URL Standard, backslashes are not valid characters in URL scheme data. URLs are defined with forward slashes (/) as delimiters — this applies to all parts of a URL, including the scheme, authority, path, query, and fragment.
This issue most commonly occurs when developers copy file paths from Windows operating systems, where backslashes are the default path separator (e.g., C:\Users\Documents\file.html), and paste them directly into HTML markup. It can also happen when server-side code generates URLs using OS-level path functions that produce backslashes on Windows.
Why this matters
- Standards compliance: The WHATWG URL Standard explicitly forbids backslashes in scheme data. Validators flag this as an error because the resulting URL is malformed.
- Cross-browser inconsistency: While some browsers may silently correct backslashes to forward slashes, this behavior is not guaranteed across all browsers or versions. Relying on browser error correction leads to fragile code.
- Broken links: Certain browsers, HTTP clients, or intermediary servers may not auto-correct the backslash, causing the link to fail entirely — resulting in 404 errors or unexpected navigation.
- Security concerns: Backslashes in URLs can be exploited in certain attack vectors like open redirects or path traversal attacks. Using well-formed URLs reduces the attack surface.
How to fix it
- Replace all backslashes (\) with forward slashes (/) in your href values.
- Check for URL generation in server-side code. If your application builds URLs programmatically, ensure it uses forward slashes regardless of the host operating system.
- Use relative or absolute URLs consistently. Whether the URL is relative (images/photo.jpg) or absolute (https://example.com/images/photo.jpg), always use forward slashes.
Examples
Incorrect: backslashes in a relative path
<a href="pages\about\team.html">Meet the Team</a>
Correct: forward slashes in a relative path
<a href="pages/about/team.html">Meet the Team</a>
Incorrect: backslashes in an absolute URL
<a href="https://example.com\blog\2024\post.html">Read the Post</a>
Correct: forward slashes in an absolute URL
<a href="https://example.com/blog/2024/post.html">Read the Post</a>
Incorrect: Windows file path pasted directly
<a href="assets\downloads\report.pdf">Download Report</a>
Correct: converted to a proper relative URL
<a href="assets/downloads/report.pdf">Download Report</a>
Incorrect: mixed slashes
Sometimes a URL contains a mix of forward and backslashes, which also triggers this error:
<a href="https://example.com/images\photos\sunset.jpg">View Photo</a>
Correct: all forward slashes
<a href="https://example.com/images/photos/sunset.jpg">View Photo</a>
A quick way to audit your HTML files is to search for \ within any href (or src, action, etc.) attribute values and replace them with /. In most code editors, you can use find-and-replace scoped to attribute values to handle this efficiently.
URLs follow strict syntax rules defined by RFC 3986. Within the path segment of a URL, only a specific set of characters is allowed: unreserved characters (letters, digits, -, ., _, ~), percent-encoded characters (like %20), and certain reserved sub-delimiters. When the W3C validator encounters a character outside this allowed set in a <link> element’s href attribute, it flags the error.
Common causes of this issue include:
- Template placeholders left in the URL, such as {{variable}} or ${path}, where curly braces and dollar signs haven’t been resolved or encoded.
- Spaces in file paths, such as href="styles/my file.css" instead of using %20 or renaming the file.
- Copy-paste errors that introduce invisible or special Unicode characters.
- Backslashes (\) used instead of forward slashes (/), which is a common mistake on Windows systems.
- Unencoded query-like characters placed in the path portion of the URL.
This matters because browsers may interpret malformed URLs inconsistently. A URL that works in one browser might fail in another. Additionally, invalid URLs can break resource loading, cause accessibility issues when assistive technologies try to process the document, and lead to unexpected behavior with proxies, CDNs, or other intermediaries that strictly parse URLs.
To fix the issue, inspect the href value reported in the error and either:
- Remove the illegal character if it was included by mistake.
- Percent-encode the character if it must be part of the URL (e.g., a space becomes %20, a pipe | becomes %7C).
- Rename the referenced file or directory to avoid special characters altogether (the simplest and most reliable approach).
Examples
Incorrect: Space in the path
<link rel="stylesheet" href="styles/my styles.css">
The space character is not allowed in a URL path segment. The validator will flag this as an illegal character.
Fixed: Percent-encode the space
<link rel="stylesheet" href="styles/my%20styles.css">
Better fix: Rename the file to avoid spaces
<link rel="stylesheet" href="styles/my-styles.css">
Incorrect: Template placeholder left unresolved
<link rel="stylesheet" href="styles/{{theme}}/main.css">
Curly braces { and } are not valid in URL path segments. This commonly happens with server-side or client-side templating syntax that wasn’t processed before the HTML was served.
Fixed: Use a valid resolved path
<link rel="stylesheet" href="styles/dark/main.css">
Incorrect: Backslash used as path separator
<link rel="stylesheet" href="styles\main.css">
Backslashes are not valid URL characters. URLs always use forward slashes.
Fixed: Use forward slashes
<link rel="stylesheet" href="styles/main.css">
Incorrect: Pipe character in the URL
<link rel="stylesheet" href="styles/font|icon.css">
Fixed: Percent-encode the pipe character
<link rel="stylesheet" href="styles/font%7Cicon.css">
Full valid document example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Webpage</title>
<link rel="stylesheet" href="styles/main.css">
<link rel="icon" href="images/favicon.ico">
</head>
<body>
<h1>Welcome to my webpage!</h1>
<p>Here is some content.</p>
</body>
</html>
When in doubt, run your URL through a URL encoder or validator separately to confirm all characters are legal. As a general best practice, stick to lowercase letters, digits, hyphens, and forward slashes in your file and directory names—this avoids encoding issues entirely and makes your URLs clean and predictable.
A space character in the href attribute of a <link> element is not valid in a URL and must be encoded as %20.
URLs follow strict syntax rules defined in RFC 3986. Spaces are not permitted anywhere in a URL, including the query string (the part after the ?). When a URL needs to represent a space, it must be percent-encoded as %20.
Browsers are forgiving and will often handle spaces by silently encoding them, but the HTML is still technically invalid. This can lead to unexpected behavior in less forgiving environments like HTML emails, web crawlers, or certain HTTP clients.
Invalid Example
<link rel="stylesheet" href="https://example.com/styles?family=Open Sans">
Fixed Example
Replace every space with %20:
<link rel="stylesheet" href="https://example.com/styles?family=Open%20Sans">
If your URL has multiple spaces or special characters, make sure each one is properly percent-encoded. Common replacements include %20 for spaces, %26 for & inside already-encoded contexts, and %3D for =. Most programming languages offer a URL-encoding function (e.g., encodeURI() in JavaScript) to handle this automatically.
The <link> element is used to define relationships between the current document and external resources — most commonly stylesheets, icons, and preloaded assets. The href attribute specifies the URL of that external resource, and it is the core purpose of the element. An empty href attribute makes the <link> element meaningless because there is no resource to fetch or reference.
Why This Is a Problem
Standards compliance: The HTML specification requires the href attribute on <link> to be a valid, non-empty URL. An empty string does not qualify as a valid URL, so the validator flags it as an error.
Unexpected browser behavior: When a browser encounters an empty href, it may resolve it relative to the current document’s URL. This means the browser could end up making an unnecessary HTTP request for the current page itself, interpreting the HTML response as a stylesheet or other resource. This wastes bandwidth, can slow down page loading, and may trigger unexpected rendering issues.
Accessibility and semantics: An empty href provides no useful information to browsers, screen readers, or other user agents about the relationship between the document and an external resource. It adds noise to the DOM without contributing anything functional.
How to Fix It
- Provide a valid URL: If the <link> element is meant to reference a resource, set href to the correct URL of that resource.
- Remove the element: If no resource is needed, remove the entire <link> element rather than leaving it with an empty href.
- Check dynamic rendering: This issue often occurs when a templating engine or CMS outputs a <link> element with a variable that resolves to an empty string. Add a conditional check so the element is only rendered when a valid URL is available.
Examples
❌ Incorrect: Empty href attribute
<link rel="stylesheet" href="">
This triggers the validation error because href is empty.
❌ Incorrect: Empty href from a template
<!-- A template variable resolved to an empty string -->
<link rel="icon" type="image/png" href="">
✅ Correct: Valid href pointing to a resource
<link rel="stylesheet" href="/css/main.css">
✅ Correct: Valid href for a favicon
<link rel="icon" type="image/png" href="/images/favicon.png">
✅ Correct: Remove the element if no resource is needed
<!-- Simply omit the <link> element entirely -->
✅ Correct: Conditional rendering in a template
If you’re using a templating language, wrap the <link> in a conditional so it only renders when a URL is available. For example, in a Jinja2-style template:
{% if stylesheet_url %}
<link rel="stylesheet" href="{{ stylesheet_url }}">
{% endif %}
This ensures the <link> element is never output with an empty href.
The id attribute uniquely identifies an element within a document. According to the WHATWG HTML living standard, if the id attribute is specified, its value must be non-empty and must not contain any ASCII whitespace characters. The attribute itself is optional — you don’t need to include it — but when you do, it must have a valid value. Setting id="" violates this rule because the empty string is not a valid identifier.
This issue commonly occurs when code is generated dynamically (e.g., by a templating engine or JavaScript framework) and the variable intended to populate the id value resolves to an empty string. It can also happen when developers add the attribute as a placeholder and forget to fill it in.
Why this matters
- Standards compliance: An empty id violates the HTML specification, making your document invalid.
- Accessibility: Assistive technologies like screen readers rely on id attributes to associate <label> elements with form controls. An empty id breaks this association, making forms harder to use for people who depend on these tools.
- JavaScript and CSS: Methods like document.getElementById("") and selectors like # (with no identifier) will not work as expected. An empty id can cause subtle, hard-to-debug issues in your scripts and styles.
- Browser behavior: While browsers are generally forgiving, an empty id leads to undefined behavior. Different browsers may handle it inconsistently.
How to fix it
- Assign a meaningful value: Give the id a descriptive, unique value that identifies the element’s purpose (e.g., id="country-select").
- Remove the attribute: If you don’t need the id, simply remove it from the element altogether.
- Fix dynamic generation: If a templating engine or framework is producing the empty value, add a conditional check to either output a valid id or omit the attribute entirely.
Examples
❌ Incorrect: empty id attribute
<label for="country">Country</label>
<select id="" name="country">
<option value="us">United States</option>
<option value="ca">Canada</option>
</select>
This triggers the validation error because id="" is an empty string.
✅ Correct: meaningful id value
<label for="country">Country</label>
<select id="country" name="country">
<option value="us">United States</option>
<option value="ca">Canada</option>
</select>
The id now has a valid, non-empty value, and the <label> element’s for attribute correctly references it.
✅ Correct: id attribute removed entirely
<label>
Country
<select name="country">
<option value="us">United States</option>
<option value="ca">Canada</option>
</select>
</label>
If you don’t need the id, remove it. Here, the <label> wraps the <select> directly, so the for/id association isn’t needed — the implicit label works just as well.
The validator reports “Bad value “” for attribute id on element X: An ID must not be the empty string” when any element includes an empty id attribute. Per the HTML standard, id is a global attribute used as a unique document-wide identifier. An empty identifier is not a valid value and is ignored by some features, leading to hard-to-debug issues.
This matters for accessibility and interoperability. Features that depend on IDs—fragment navigation (#target), <label for>, ARIA attributes like aria-labelledby/aria-controls, and DOM APIs such as document.getElementById()—require a non-empty, unique value. Empty IDs break these links, can degrade assistive technology output, and violate conformance, which may hide bugs across browsers.
How to fix:
- If the element doesn’t need an identifier, remove the id attribute entirely.
- If it needs one, provide a non-empty, unique value, e.g., id="main-content".
- Ensure uniqueness across the page; each id must occur only once.
- Use simple, predictable tokens: avoid spaces, prefer lowercase letters, digits, hyphens, and underscores (e.g., feature-1). While the spec allows a broad range of characters, sticking to URL- and selector-friendly characters avoids pitfalls.
Examples
Example that triggers the validator error (empty id)
<div id=""></div>
Correct: remove an unnecessary empty id
<div></div>
Correct: provide a meaningful, unique id
<section id="features"></section>
Problematic label association with empty id (invalid)
<label for="">Email</label>
<input type="email" id="">
Correct label–control association
<label for="email">Email</label>
<input type="email" id="email">
Correct ARIA relationship
<h2 id="pricing-heading">Pricing</h2>
<section aria-labelledby="pricing-heading">
<p>Choose a plan.</p>
</section>
Correct fragment navigation target
<nav>
<a href="#contact">Contact</a>
</nav>
<section id="contact">
<h2>Contact us</h2>
</section>
Minimal full document (validated) demonstrating proper ids
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Valid IDs Example</title>
</head>
<body>
<main id="main-content">
<h1 id="page-title">Welcome</h1>
<p>Jump to the <a href="#details">details</a>.</p>
<section id="details">
<h2>Details</h2>
</section>
<form>
<label for="email">Email</label>
<input id="email" type="email">
</form>
</main>
</body>
</html>
The imagesizes attribute is used exclusively on <link> elements that have rel="preload" and as="image". It works in tandem with the imagesrcset attribute to allow the browser to preload the most appropriate image from a set of candidates — mirroring how sizes and srcset work on an <img> element. When the browser encounters these attributes on a <link>, it can begin fetching the right image resource early, before it even parses the <img> tag in the document body.
When imagesizes is set to an empty string (""), the browser has no information about the intended display size of the image, which defeats the purpose of responsive image preloading. The browser cannot select the best candidate from imagesrcset without knowing how large the image will be rendered. An empty value is invalid per the HTML specification, which requires the attribute to contain a valid source size list (the same syntax used by the sizes attribute on <img>).
This matters for both performance and standards compliance. Responsive preloading is a performance optimization — an empty imagesizes undermines that by leaving the browser unable to make an informed choice. From a standards perspective, the validator correctly rejects the empty value because the attribute’s defined value space does not include the empty string.
How to fix it
- Provide a valid sizes value that matches the sizes attribute on the corresponding <img> element in your page. This tells the browser how wide the image will be at various viewport widths.
- Remove imagesizes entirely if you don’t need responsive preloading. If you’re preloading a single image (using href instead of imagesrcset), you don’t need imagesizes at all.
Examples
❌ Bad: empty imagesizes attribute
<link
rel="preload"
as="image"
imagesrcset="hero-480.jpg 480w, hero-800.jpg 800w, hero-1200.jpg 1200w"
imagesizes="">
The empty imagesizes="" triggers the validation error and prevents the browser from selecting the correct image candidate.
✅ Fixed: providing a valid sizes value
<link
rel="preload"
as="image"
imagesrcset="hero-480.jpg 480w, hero-800.jpg 800w, hero-1200.jpg 1200w"
imagesizes="(max-width: 600px) 480px, (max-width: 1000px) 800px, 1200px">
The imagesizes value uses the same syntax as the sizes attribute on <img>. It provides media conditions paired with lengths, with a fallback length at the end. This value should match the sizes attribute on the corresponding <img> element in your markup.
✅ Fixed: simple full-width image
<link
rel="preload"
as="image"
imagesrcset="banner-640.jpg 640w, banner-1280.jpg 1280w"
imagesizes="100vw">
If the image spans the full viewport width, 100vw is a straightforward and valid value.
✅ Fixed: removing the attribute when not needed
<link rel="preload" as="image" href="logo.png">
If you’re preloading a single, non-responsive image, omit both imagesrcset and imagesizes and use the href attribute instead. The imagesizes attribute is only meaningful when paired with imagesrcset.
The imagesrcset attribute is used exclusively on <link> elements that have rel="preload" and as="image". It mirrors the srcset attribute of the <img> element, allowing the browser to preload the most appropriate image resource based on the current viewport and display conditions. When the validator encounters imagesrcset="" (an empty value), it reports this error because an empty string is not a valid source set — it must contain at least one image candidate string.
Each image candidate string in the imagesrcset value consists of a URL followed by an optional width descriptor (e.g., 480w) or pixel density descriptor (e.g., 2x). Multiple candidates are separated by commas. This is the same syntax used by the srcset attribute on <img> elements.
This issue typically arises when a CMS, static site generator, or templating engine outputs the imagesrcset attribute with an empty value — for example, when a responsive image field has no data. Browsers may ignore the malformed attribute, but it results in invalid HTML, can cause unexpected preloading behavior, and signals that the page’s resource hints are misconfigured. Fixing it ensures standards compliance and that the browser’s preload scanner works as intended.
How to fix it
- Provide a valid source set — populate imagesrcset with one or more image candidate strings.
- Remove the attribute — if you don’t have multiple image sources to preload, remove imagesrcset (and imagesizes) from the <link> element entirely. You can still preload a single image using just the href attribute.
- Conditionally render — if your templating system might produce an empty value, add logic to omit the attribute when no responsive sources are available.
When using imagesrcset, you should also include the imagesizes attribute (mirroring the sizes attribute on <img>) so the browser can select the correct candidate based on layout information.
Examples
❌ Empty imagesrcset triggers the error
<link rel="preload" as="image" href="hero.jpg" imagesrcset="" imagesizes="">
The empty imagesrcset value is invalid and produces the W3C validation error.
✅ Valid imagesrcset with width descriptors
<link
rel="preload"
as="image"
href="hero-800.jpg"
imagesrcset="hero-400.jpg 400w, hero-800.jpg 800w, hero-1200.jpg 1200w"
imagesizes="(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px">
This tells the browser to preload the most appropriate image based on the viewport width, matching the responsive behavior of the corresponding <img> element on the page.
✅ Valid imagesrcset with pixel density descriptors
<link
rel="preload"
as="image"
href="logo.png"
imagesrcset="logo.png 1x, logo@2x.png 2x">
This preloads the correct logo variant based on the device’s pixel density.
✅ Removing the attribute when no responsive sources exist
<link rel="preload" as="image" href="hero.jpg">
If you only have a single image to preload, simply use href without imagesrcset. This is valid and avoids the error entirely.
✅ Conditional rendering in a template
If you’re using a templating language, conditionally include the attribute:
<!-- Pseudocode example -->
<link
rel="preload"
as="image"
href="hero.jpg"
{% if responsive_sources %}
imagesrcset="{{ responsive_sources }}"
imagesizes="{{ image_sizes }}"
{% endif %}>
This prevents the attribute from being rendered with an empty value when no responsive image data is available.
The max attribute defines the maximum value that is acceptable and valid for the input containing it. When the browser encounters max="", it expects a valid floating-point number as defined by the HTML specification. An empty string cannot be parsed as a number, so the attribute becomes meaningless and triggers a validation error.
This commonly happens when HTML is generated dynamically by a template engine or framework that outputs an empty value for max when no maximum has been configured. It can also occur when a developer adds the attribute as a placeholder intending to fill it in later.
While most browsers will silently ignore an invalid max value, relying on this behavior is problematic for several reasons:
- Standards compliance: The HTML specification requires max to be a valid floating-point number when present.
- Predictable validation: An empty max means no client-side maximum constraint is enforced, which may not be the developer’s intent. Explicitly removing the attribute makes that intention clear.
- Accessibility: Assistive technologies may read the max attribute to communicate input constraints to users. An empty value could lead to confusing or undefined behavior.
This error applies to input types that accept numeric-style max values, including number, range, date, datetime-local, month, week, and time.
How to Fix It
- Set a valid numeric value: If you need a maximum constraint, provide a proper floating-point number (e.g., max="100" or max="99.5").
- Remove the attribute: If no maximum is needed, remove the max attribute entirely rather than leaving it empty.
- Fix dynamic templates: If your HTML is generated from a template, add a conditional check so that max is only rendered when a value is actually available.
Examples
❌ Invalid: Empty max attribute
<label for="quantity">Quantity:</label>
<input type="number" id="quantity" name="quantity" max="">
The empty string "" is not a valid floating-point number, so this triggers the validation error.
✅ Fixed: Providing a valid numeric value
<label for="quantity">Quantity:</label>
<input type="number" id="quantity" name="quantity" max="100">
✅ Fixed: Removing the attribute entirely
<label for="quantity">Quantity:</label>
<input type="number" id="quantity" name="quantity">
If no maximum constraint is needed, simply omit the max attribute.
❌ Invalid: Empty max on a date input
<label for="end-date">End date:</label>
<input type="date" id="end-date" name="end-date" max="">
✅ Fixed: Valid date value for max
<label for="end-date">End date:</label>
<input type="date" id="end-date" name="end-date" max="2025-12-31">
For date-related input types, the max value must be in the appropriate date/time format (e.g., YYYY-MM-DD for type="date").
Fixing dynamic templates
If you’re generating HTML with a templating language, conditionally include the attribute only when a value exists. For example, in a Jinja2-style template:
<input type="number" id="price" name="price"
{% if max_price %}max="{{ max_price }}"{% endif %}>
This ensures the max attribute is only rendered when max_price has a valid value, avoiding the empty-string problem entirely.
The HTML specification defines maxlength as accepting only a valid non-negative integer — a string of one or more ASCII digits representing a number greater than or equal to zero. An empty string does not satisfy this requirement, so the W3C validator flags it as an error. This commonly happens when a value is dynamically generated by a CMS, template engine, or JavaScript framework and the value ends up blank, or when a developer adds the attribute as a placeholder intending to fill it in later.
Why this matters
While most browsers silently ignore an empty maxlength and impose no character limit, relying on this behavior is problematic for several reasons:
- Standards compliance: Invalid HTML can lead to unpredictable behavior across different browsers and versions. What works today may not work tomorrow.
- Accessibility: Assistive technologies may read or interpret the maxlength attribute to communicate input constraints to users. An empty value could cause confusing or incorrect announcements.
- Maintainability: An empty maxlength is ambiguous — it’s unclear whether the developer intended no limit, forgot to set a value, or a bug caused the value to be missing.
How to fix it
You have two options:
- Set a valid non-negative integer: Provide a concrete number that represents the maximum number of characters the user can enter, such as maxlength="100".
- Remove the attribute: If you don’t need to enforce a character limit, simply omit maxlength altogether. There is no need to include it with an empty value.
Valid values for maxlength include "0", "1", "255", or any other non-negative whole number. The following are not valid: empty strings (""), negative numbers ("-1"), decimal numbers ("10.5"), or non-numeric strings ("none").
Where maxlength applies
The maxlength attribute is meaningful on text-entry input types: text, search, url, tel, email, password, and also on the textarea element. For non-text input types like number, date, range, or checkbox, the attribute has no effect and should not be used.
Examples
❌ Incorrect: empty string triggers the validation error
<input type="text" name="username" maxlength="">
❌ Incorrect: other invalid values
<input type="text" name="username" maxlength="-1">
<input type="email" name="email" maxlength="none">
<input type="text" name="bio" maxlength="10.5">
✅ Correct: explicit maximum length
<input type="text" name="username" maxlength="30">
✅ Correct: omit the attribute when no limit is needed
<input type="text" name="comment">
✅ Correct: maxlength on a textarea
<textarea name="bio" maxlength="500"></textarea>
✅ Correct: dynamic value with a fallback
If your maxlength value comes from a template or CMS, make sure you either output a valid number or omit the attribute entirely. For example, in a templating language, use conditional logic:
<!-- Only render maxlength if the value is set -->
<input type="text" name="username" maxlength="100">
Rather than rendering an empty attribute like maxlength="", ensure your template skips the attribute when no value is configured.
The maxlength attribute controls the maximum number of characters a user can type into a <textarea>. According to the HTML specification, its value must be a valid non-negative integer — that is, a string of one or more ASCII digits like 0, 100, or 5000. An empty string (""), whitespace, negative numbers, or non-numeric values are all invalid. When the browser encounters an invalid maxlength value, its behavior becomes unpredictable — some browsers may ignore the attribute, while others may silently enforce no limit, leading to inconsistent form behavior across platforms.
This issue frequently arises when a server-side template or JavaScript framework conditionally outputs the maxlength attribute but produces an empty value when no limit is configured. For example, a template like maxlength="{{ maxChars }}" will render maxlength="" if the maxChars variable is empty or undefined. The fix is to ensure the attribute is omitted entirely when no value is available, rather than rendering it with an empty string.
Omitting maxlength allows unlimited input. Setting it to 0 is technically valid but prevents the user from entering any characters at all, which is rarely useful. Choose a value that makes sense for your use case, such as the corresponding database column’s character limit.
Why this matters
- Standards compliance: The HTML specification explicitly requires a valid non-negative integer. An empty string violates this rule and produces a validation error.
- Consistent behavior: Browsers handle invalid attribute values differently. A valid value ensures the character limit works reliably across all browsers.
- Accessibility: Screen readers and assistive technologies may announce the maximum character limit to users. An empty or invalid value could cause confusing announcements or be silently ignored.
- Form reliability: If your application depends on maxlength for client-side input restrictions (e.g., to match a database column limit), an invalid value means the constraint isn’t enforced, potentially leading to data truncation or server errors.
How to fix it
- Set a specific integer value if you need a character limit: maxlength="200".
- Remove the attribute entirely if no limit is needed. An absent maxlength means unlimited input.
- Fix your templates — if you’re using a server-side language or JavaScript framework, conditionally render the attribute so it’s omitted when no value is provided rather than output as empty.
Examples
❌ Invalid: empty maxlength value
The empty string is not a valid non-negative integer, so this triggers the validation error.
<label for="msg">Message</label>
<textarea id="msg" name="message" maxlength=""></textarea>
❌ Invalid: non-numeric maxlength value
Strings, decimals, and negative numbers are also invalid.
<label for="bio">Bio</label>
<textarea id="bio" name="bio" maxlength="none"></textarea>
<label for="notes">Notes</label>
<textarea id="notes" name="notes" maxlength="-1"></textarea>
✅ Fixed: specific integer value
Set maxlength to the desired character limit.
<label for="msg">Message (max 200 characters)</label>
<textarea id="msg" name="message" maxlength="200"></textarea>
✅ Fixed: attribute omitted entirely
If no character limit is needed, simply remove the attribute.
<label for="msg">Message</label>
<textarea id="msg" name="message"></textarea>
✅ 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 depends on your framework — here’s a conceptual example:
<!-- Instead of always outputting the attribute: -->
<!-- <textarea maxlength="{{ maxChars }}"></textarea> -->
<!-- Only render it when maxChars has a value: -->
<!-- {% if maxChars %}<textarea maxlength="{{ maxChars }}"></textarea>{% endif %} -->
<label for="feedback">Feedback</label>
<textarea id="feedback" name="feedback" maxlength="500"></textarea>
The min attribute defines the minimum acceptable value for form input types such as number, range, date, time, datetime-local, week, and month. When the browser or the W3C validator encounters min="", it attempts to parse the empty string as a floating point number and fails because the empty string is not a valid representation of any number according to the HTML specification’s rules for parsing floating point numbers.
This issue commonly arises when templating engines or server-side code dynamically set the min attribute but output an empty value when no minimum is configured, or when developers add the attribute as a placeholder intending to fill it in later.
Why this matters
- Standards compliance: The HTML specification explicitly requires the min attribute’s value to be a valid floating point number (for numeric types) or a valid date/time string (for date/time types). An empty string satisfies neither requirement.
- Unpredictable browser behavior: When browsers encounter an invalid min value, they typically ignore the attribute entirely. This means your intended constraint silently disappears, potentially allowing users to submit out-of-range values.
- Accessibility concerns: Assistive technologies may rely on min and max to communicate valid input ranges to users. An invalid value can lead to confusing or missing guidance for screen reader users.
- Form validation issues: Built-in browser validation using the Constraint Validation API depends on valid min values. An empty string can cause the browser’s native validation to behave inconsistently across different browsers.
How to fix it
You have two straightforward options:
- Provide a valid value: Set min to the actual minimum number or date/time string you want to enforce.
- Remove the attribute: If no minimum constraint is needed, simply omit the min attribute. The input will then accept any value within its type’s natural range.
If your min value comes from dynamic server-side or JavaScript logic, make sure the attribute is only rendered when a valid value is available, rather than outputting an empty string as a fallback.
Examples
❌ Invalid: empty string for min
<input type="number" min="" max="10">
The empty string "" is not a valid floating point number, so this triggers the validation error.
✅ Fixed: provide a valid number
<input type="number" min="0" max="10">
✅ Fixed: remove min if no minimum is needed
<input type="number" max="10">
❌ Invalid: empty min on a range input
<input type="range" min="" max="100" step="5">
✅ Fixed: valid min on a range input
<input type="range" min="0" max="100" step="5">
❌ Invalid: empty min on a date input
<input type="date" min="" max="2025-12-31">
For date inputs, min must be a valid date string in YYYY-MM-DD format — an empty string is equally invalid here.
✅ Fixed: valid min on a date input
<input type="date" min="2025-01-01" max="2025-12-31">
Handling dynamic values in templates
If you’re using a templating language and the minimum value might not always exist, conditionally render the attribute rather than outputting an empty value. For example, in a generic template pseudocode:
<!-- Instead of always outputting the attribute: -->
<input type="number" min="" max="10">
<!-- Only include it when a value is available: -->
<input type="number" min="5" max="10">
In practice, use your templating engine’s conditional logic (e.g., {% if min_value %}min="{{ min_value }}"{% endif %} in Jinja2, or similar constructs) to ensure min is only present when it holds a valid value.
The name attribute on <a> elements was historically used to create named anchors — fragment targets that could be linked to with href="#anchorName". In modern HTML (the WHATWG living standard), the name attribute on <a> is considered obsolete for this purpose. The id attribute is now the standard way to create fragment targets, and it can be placed on any element, not just <a> tags.
Regardless of whether you use name or id, the value must be a non-empty string. The W3C validator enforces this rule because an empty identifier serves no functional purpose — it cannot be referenced by a fragment link, it cannot be targeted by JavaScript, and it creates invalid markup. Browsers may silently ignore it, but it pollutes the DOM and signals a likely mistake in the code.
Empty name attributes often appear in content migrated from older CMS platforms or WYSIWYG editors that inserted placeholder anchors like <a name=""></a>. They can also result from templating systems where a variable intended to populate the attribute resolved to an empty string.
Why this matters
- Standards compliance: Both the WHATWG HTML living standard and the W3C HTML specification require that identifier-like attributes (id, name) must not be empty strings.
- Accessibility: Screen readers and assistive technologies may attempt to process named anchors. Empty identifiers create noise without providing any navigational value.
- Functionality: An empty name or id cannot be used as a fragment target, so the element is effectively useless as a link destination.
How to fix it
- Remove the element entirely if the empty anchor serves no purpose — this is the most common fix.
- Replace name with id and provide a meaningful, non-empty value if you need a fragment target.
- Move the id to a nearby semantic element instead of using a standalone empty <a> tag. For example, place the id directly on a heading, section, or paragraph.
- Ensure uniqueness — every id value in a document must be unique.
Examples
❌ Empty name attribute triggers the error
<a name=""></a>
<h2>Introduction</h2>
<p>Welcome to the guide.</p>
❌ Empty name generated by a template
<a name=""></a>
<p>This anchor was meant to be a target but the value is missing.</p>
<a href="#">Jump to section</a>
✅ Remove the empty anchor if it’s unnecessary
<h2>Introduction</h2>
<p>Welcome to the guide.</p>
✅ Use id on the target element directly
<h2 id="introduction">Introduction</h2>
<p>Welcome to the guide.</p>
<!-- Link to the section from elsewhere -->
<a href="#introduction">Go to Introduction</a>
✅ Use id on a standalone anchor if needed
If you need a precise anchor point that doesn’t correspond to an existing element, use an <a> tag with a valid, non-empty id:
<a id="section-start"></a>
<p>This paragraph follows the anchor point.</p>
<a href="#section-start">Jump to section start</a>
✅ Migrate legacy name to id
If your existing code uses the obsolete name attribute with a valid value, update it to use id instead:
<!-- Before (obsolete but was valid in HTML4) -->
<a name="contact"></a>
<!-- After (modern HTML) -->
<a id="contact"></a>
<!-- Even better: put the id on a semantic element -->
<h2 id="contact">Contact Us</h2>
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