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 W3C HTML Validator checks that URLs provided in href attributes conform to the URL specification. Square brackets ([ and ]) are reserved characters with very specific, limited uses in URLs — they are only permitted in the host portion of a URL to denote IPv6 addresses (e.g., http://[::1]/). When they appear elsewhere, such as in the scheme data, path, or query string without being percent-encoded, the URL is considered malformed.
This commonly happens in a few scenarios:
- Mailto links where someone wraps an email address in brackets, like mailto:[user@example.com].
- Template variables that haven’t been processed, leaving literal bracket syntax (e.g., {{, [, ]) in the rendered HTML.
- Manually constructed URLs where brackets are mistakenly used as part of the path or query string instead of being percent-encoded as %5B and %5D.
Using invalid URLs can cause browsers to misinterpret the link destination, break navigation, or cause unexpected behavior. Assistive technologies such as screen readers also rely on well-formed URLs to correctly communicate link targets to users. Keeping your URLs standards-compliant ensures consistent, predictable behavior across all browsers and devices.
How to fix it
- Remove unnecessary brackets. If the brackets are not part of the actual data (e.g., decorative brackets around an email address), simply delete them.
- Percent-encode brackets when they are part of the data. If you genuinely need square brackets in a URL’s path or query string, encode [ as %5B and ] as %5D.
- Check your templating engine output. If you use a templating system, inspect the final rendered HTML in a browser to make sure template syntax has been fully replaced with valid values.
Examples
Invalid: square brackets in a mailto URL
The brackets around the email address are not valid URL characters in this context.
<a href="mailto:[user@example.com]">Email Us</a>
Fixed: remove the brackets
<a href="mailto:user@example.com">Email Us</a>
Invalid: square brackets in a query string
<a href="https://example.com/search?filter[status]=active">Search</a>
Fixed: percent-encode the brackets
<a href="https://example.com/search?filter%5Bstatus%5D=active">Search</a>
Invalid: unprocessed template syntax in rendered HTML
If your templating engine fails to replace a variable, the final HTML may contain bracket characters:
<a href="mailto:[% user.email %]">Email Us</a>
Fixed: ensure the template renders a valid URL
Make sure the template variable resolves correctly. The rendered output should look like:
<a href="mailto:user@example.com">Email Us</a>
In your template source, this might be written as:
<a href="mailto:{{ user.email }}">Email Us</a>
The key is that the final HTML delivered to the browser must contain a valid, bracket-free URL (unless the brackets are properly percent-encoded). Always validate your rendered output, not just your template source, to catch issues like this.
The <iframe> element embeds another HTML document within the current page, and its src attribute specifies the URL of the content to load. According to the URL Living Standard and RFC 3986, URLs have a strict set of allowed characters. The space character (U+0020) is not one of them — it must always be percent-encoded when it appears in any part of a URL.
When the W3C HTML Validator encounters a literal space in the query portion of an <iframe>‘s src attribute, it raises this error because the browser has to guess what you meant. While most modern browsers will silently encode the space for you, relying on this behavior is problematic for several reasons:
- Standards compliance: The HTML specification requires that the src attribute contain a valid URL. A URL with literal spaces is not valid.
- Interoperability: Different browsers, HTTP clients, and intermediary servers may handle unencoded spaces differently. Some might truncate the URL at the first space, while others might encode it. This inconsistency can lead to broken embeds.
- Copy-paste and sharing: If a user or script extracts the URL from the HTML source, the unencoded space may cause errors in contexts that don’t perform automatic encoding.
To fix this, replace every literal space in the URL with %20. This is the standard percent-encoding for the space character. In query strings, you can also use + as an alternative encoding for spaces (this is common in application/x-www-form-urlencoded format), though %20 is universally accepted in all parts of a URL.
If you’re generating <iframe> URLs dynamically with JavaScript, use the built-in encodeURIComponent() function to encode individual query parameter values, or use the URL and URLSearchParams APIs, which handle encoding automatically.
Examples
❌ Invalid: literal spaces in the query string
<iframe src="https://maps.google.com/maps?q=2700 6th Avenue"></iframe>
The space between 2700 and 6th and between 6th and Avenue triggers the validation error.
✅ Fixed: spaces encoded as %20
<iframe src="https://maps.google.com/maps?q=2700%206th%20Avenue"></iframe>
✅ Fixed: spaces encoded as + in the query string
<iframe src="https://maps.google.com/maps?q=2700+6th+Avenue"></iframe>
Both %20 and + are valid encodings for spaces in query strings.
❌ Invalid: spaces in multiple query parameters
<iframe
src="https://example.com/embed?title=My Page&city=New York">
</iframe>
✅ Fixed: all spaces properly encoded
<iframe
src="https://example.com/embed?title=My%20Page&city=New%20York">
</iframe>
Using JavaScript to build encoded URLs
If you construct iframe URLs dynamically, let the browser handle encoding for you:
const url = new URL("https://maps.google.com/maps");
url.searchParams.set("q", "2700 6th Avenue");
const iframe = document.createElement("iframe");
iframe.src = url.toString();
// Result: "https://maps.google.com/maps?q=2700+6th+Avenue"
The URLSearchParams API automatically encodes spaces (as +), along with any other special characters, ensuring the resulting URL is always valid.
URLs follow strict syntax rules defined by RFC 3986, which does not permit literal space characters anywhere in a URI. When the W3C validator encounters a space in the href attribute of an <a> element — particularly within the query string (the part after the ?) — it flags it as an illegal character.
While most modern browsers will silently fix malformed URLs by encoding spaces for you, relying on this behavior is problematic for several reasons:
- Standards compliance: The HTML specification requires that href values contain valid URLs. A space makes the URL syntactically invalid.
- Interoperability: Not all user agents, crawlers, or HTTP clients handle malformed URLs the same way. Some may truncate the URL at the first space or reject it entirely.
- Accessibility: Screen readers and assistive technologies may struggle to interpret or announce links with invalid URLs.
- Link sharing and copy-pasting: If a user copies the link from the source or if the URL is used in an API or redirect, the unencoded space can cause breakage.
To fix this issue, replace every literal space character in the URL with %20. Within query string values, you can also use + as a space encoding (this is the application/x-www-form-urlencoded format commonly used in form submissions). If you’re generating URLs dynamically, use your programming language’s URL encoding function (e.g., encodeURIComponent() in JavaScript, urlencode() in PHP, or urllib.parse.quote() in Python).
Examples
Incorrect — spaces in the query string
<a href="https://example.com/search?query=hello world&lang=en">Search</a>
The space between hello and world is an illegal character in the URL.
Correct — space encoded as %20
<a href="https://example.com/search?query=hello%20world&lang=en">Search</a>
Correct — space encoded as + in query string
<a href="https://example.com/search?query=hello+world&lang=en">Search</a>
Using + to represent a space is valid within query strings and is commonly seen in URLs generated by HTML forms.
Incorrect — spaces in the path and query
<a href="https://example.com/my folder/page?name=John Doe">Profile</a>
Correct — all spaces properly encoded
<a href="https://example.com/my%20folder/page?name=John%20Doe">Profile</a>
Generating safe URLs in JavaScript
If you’re building URLs dynamically, use encodeURIComponent() for individual parameter values:
<script>
const query = "hello world";
const url = "https://example.com/search?query=" + encodeURIComponent(query);
// Result: "https://example.com/search?query=hello%20world"
</script>
Note that encodeURIComponent() encodes spaces as %20, which is safe for use in any part of a URL. Avoid using encodeURI() for query values, as it does not encode certain characters like & and = that may conflict with query string syntax.
The ARIA specification defines a strict ownership hierarchy for table-related roles. A role="cell" element must be “owned by” an element with role="row", meaning it must either be a direct child of that element or be associated with it via the aria-owns attribute. This mirrors how native HTML tables work: a <td> element must live inside a <tr> element. When you use ARIA roles to build custom table structures from non-table elements like <div> or <span>, you are responsible for maintaining this same hierarchy manually.
The expected nesting order for an ARIA table is:
- role="table" — the outermost container
- role="rowgroup" (optional) — groups rows together, like <thead>, <tbody>, or <tfoot>
- role="row" — a single row of cells
- role="cell" or role="columnheader" / role="rowheader" — individual cells
When a role="cell" element is placed directly inside a role="table" or any other container that isn’t role="row", screen readers lose the ability to announce row and column positions. Users who rely on table navigation shortcuts (such as moving between cells with arrow keys) will find the table unusable. This is not just a validation concern — it directly impacts whether people can access your content.
This issue commonly arises when developers add intermediate wrapper elements for styling purposes and accidentally break the required parent-child relationship, or when they forget to assign role="row" to a container element.
How to Fix
Ensure every element with role="cell" is a direct child of an element with role="row". If you have wrapper elements between the row and cell for layout or styling, either remove them, move the role assignments, or use aria-owns on the row element to explicitly claim ownership of the cells.
Examples
Incorrect — Cell Without a Row Parent
This triggers the validation error because role="cell" elements are direct children of the role="table" container, with no role="row" in between.
<div role="table">
<div role="cell">Name</div>
<div role="cell">Email</div>
</div>
Incorrect — Intermediate Wrapper Breaking Ownership
Here, a styling wrapper sits between the row and its cells. Since the <div> without a role is not a role="row", the cells are not properly owned.
<div role="table">
<div role="row">
<div class="cell-wrapper">
<div role="cell">Row 1, Cell 1</div>
<div role="cell">Row 1, Cell 2</div>
</div>
</div>
</div>
Correct — Cells Directly Inside Rows
Each role="cell" is a direct child of a role="row" element, forming a valid ARIA table structure.
<div role="table" aria-label="Team members">
<div role="row">
<div role="columnheader">Name</div>
<div role="columnheader">Email</div>
</div>
<div role="row">
<div role="cell">Alice</div>
<div role="cell">alice@example.com</div>
</div>
<div role="row">
<div role="cell">Bob</div>
<div role="cell">bob@example.com</div>
</div>
</div>
Correct — Using Rowgroups
You can optionally group rows with role="rowgroup", similar to <thead> and <tbody>. The cells still must be direct children of their rows.
<div role="table" aria-label="Quarterly results">
<div role="rowgroup">
<div role="row">
<div role="columnheader">Quarter</div>
<div role="columnheader">Revenue</div>
</div>
</div>
<div role="rowgroup">
<div role="row">
<div role="cell">Q1</div>
<div role="cell">$10,000</div>
</div>
<div role="row">
<div role="cell">Q2</div>
<div role="cell">$12,500</div>
</div>
</div>
</div>
Correct — Using Native HTML Instead
If your content is genuinely tabular data, consider using native HTML table elements instead of ARIA roles. Native tables have built-in semantics and require no additional role attributes.
<table>
<tr>
<th>Name</th>
<th>Email</th>
</tr>
<tr>
<td>Alice</td>
<td>alice@example.com</td>
</tr>
</table>
Native HTML tables are always preferable when they suit your use case. The first rule of ARIA is: if you can use a native HTML element that already has the semantics you need, use it instead of adding ARIA roles to a generic element.
Percent-encoding (also called URL encoding) is the mechanism defined by RFC 3986 for representing special or reserved characters in URLs. It works by replacing a character with a % sign followed by two hexadecimal digits that correspond to the character’s byte value. For example, a space becomes %20, an ampersand becomes %26, and a literal percent sign becomes %25. When a browser or parser encounters % in a URL, it expects the next two characters to be valid hexadecimal digits so it can decode them back to the original character.
If the parser finds a % that is not followed by two hexadecimal digits, it cannot decode the sequence, and the URL is considered invalid. The W3C validator flags this as a bad value for the href attribute.
Common causes
- A literal % that wasn’t encoded. This is the most frequent cause. For instance, a URL containing a percentage value like 50% in a query string — the % must be written as %25.
- Truncated percent-encoded sequences. A sequence like %2 is incomplete because it only has one hex digit instead of two.
- Invalid hex characters after %. A sequence like %GZ is invalid because G and Z are not hexadecimal digits (valid hex digits are 0–9 and A–F).
- Copy-pasting URLs from non-web sources. URLs pasted from documents, emails, or spreadsheets may contain unencoded special characters.
Why this matters
- Broken links. Browsers may interpret the malformed URL differently than intended, leading users to the wrong page or a 404 error.
- Standards compliance. The WHATWG URL Standard and the HTML specification require that href values contain valid URLs. Malformed URLs violate these standards.
- Accessibility. Screen readers and assistive technologies rely on well-formed URLs to provide accurate navigation information to users.
- Interoperability. While some browsers attempt error recovery on malformed URLs, behavior is not guaranteed to be consistent across all browsers and tools.
How to fix it
- Encode literal % as %25. If the % is meant to appear as part of the URL’s content (e.g., in a query parameter value), replace it with %25.
- Complete any truncated sequences. If you intended a percent-encoded character like %20, make sure both hex digits are present.
- Use proper URL encoding functions. In JavaScript, use encodeURIComponent() for query parameter values. In server-side languages, use the equivalent encoding function (e.g., urllib.parse.quote() in Python, urlencode() in PHP).
Examples
Literal % not encoded
<!-- ❌ Bad: the % in "48%" is not followed by two hex digits -->
<a href="https://example.com?width=48%">48% width</a>
<!-- ✅ Good: the % is encoded as %25 -->
<a href="https://example.com?width=48%25">48% width</a>
Truncated percent-encoded sequence
<!-- ❌ Bad: %2 is incomplete — it needs two hex digits -->
<a href="https://example.com/path%2file">Download</a>
<!-- ✅ Good: %2F is a complete encoding for "/" -->
<a href="https://example.com/path%2Ffile">Download</a>
Invalid hexadecimal characters
<!-- ❌ Bad: %XY contains non-hexadecimal characters -->
<a href="https://example.com/search?q=%XYdata">Search</a>
<!-- ✅ Good: if the intent was a literal "%XY", encode the % -->
<a href="https://example.com/search?q=%25XYdata">Search</a>
Multiple unencoded % in a URL
<!-- ❌ Bad: both % signs are unencoded -->
<a href="https://example.com?min=10%&max=90%">Range</a>
<!-- ✅ Good: both % signs are properly encoded -->
<a href="https://example.com?min=10%25&max=90%25">Range</a>
The srcset attribute allows you to specify multiple image sources so the browser can choose the most appropriate one based on the user’s device characteristics, such as screen resolution or viewport width. When you include a srcset attribute on an <img> element, the HTML specification requires it to contain one or more comma-separated image candidate strings. Each string consists of a URL followed by an optional descriptor — either a width descriptor (e.g., 200w) or a pixel density descriptor (e.g., 2x).
This validation error typically appears when:
- The srcset attribute is empty (srcset="")
- The srcset attribute contains only whitespace (srcset=" ")
- The value contains syntax errors such as missing URLs, invalid descriptors, or incorrect formatting
- A templating engine or CMS outputs the attribute with no value
This matters because browsers rely on the srcset value to select the best image to load. An empty or malformed srcset means the browser must fall back entirely to the src attribute, making the srcset attribute pointless. Additionally, invalid markup can cause unexpected behavior across different browsers and undermines standards compliance.
How to fix it
- Provide valid image candidate strings. Each entry needs a URL and optionally a width or pixel density descriptor, with entries separated by commas.
- Remove the attribute entirely if you don’t have multiple image sources to offer. A plain src attribute is perfectly fine on its own.
- Check dynamic output. If a CMS or templating system generates the srcset, ensure it conditionally omits the attribute when no responsive image candidates are available, rather than outputting an empty attribute.
Examples
❌ Empty srcset attribute
<img src="/img/photo.jpg" alt="A sunset over the ocean" srcset="">
This triggers the error because srcset is present but contains no image candidate strings.
❌ Malformed srcset value
<img src="/img/photo.jpg" alt="A sunset over the ocean" srcset="1x, 2x">
This is invalid because each candidate string must include a URL. Descriptors alone are not valid entries.
✅ Using pixel density descriptors
<img
src="/img/photo-400.jpg"
alt="A sunset over the ocean"
srcset="
/img/photo-400.jpg 1x,
/img/photo-800.jpg 2x
">
Each candidate string contains a URL followed by a pixel density descriptor (1x, 2x). The browser picks the best match for the user’s display.
✅ Using width descriptors with sizes
<img
src="/img/photo-400.jpg"
alt="A sunset over the ocean"
srcset="
/img/photo-400.jpg 400w,
/img/photo-800.jpg 800w,
/img/photo-1200.jpg 1200w
"
sizes="(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px">
Width descriptors (e.g., 400w) tell the browser the intrinsic width of each image. The sizes attribute then tells the browser how large the image will be displayed at various viewport sizes, allowing it to calculate the best source to download.
✅ Removing srcset when not needed
<img src="/img/photo.jpg" alt="A sunset over the ocean">
If you only have a single image source, simply omit srcset altogether. The src attribute alone is valid and sufficient.
✅ Single candidate in srcset
<img
src="/img/photo.jpg"
alt="A sunset over the ocean"
srcset="/img/photo-highres.jpg 2x">
Even a single image candidate string is valid. Here, the browser will use the high-resolution image on 2x displays and fall back to src otherwise.
The dir attribute specifies the base text direction for the content of an element. When a document is written in a right-to-left language like Arabic, Hebrew, Persian (Farsi), or Urdu, the browser needs to know that text should flow from right to left. Without this attribute, browsers default to left-to-right (ltr) rendering, which can cause a range of problems: text alignment may be incorrect, punctuation can appear in the wrong place, and the overall page layout may look broken or confusing to native readers.
This matters for several important reasons:
- Accessibility: Screen readers and other assistive technologies rely on the dir attribute to correctly announce and navigate content. Without it, the reading experience for users relying on these tools may be disorienting or incorrect.
- Visual correctness: Elements like lists, tables, form labels, and navigation menus will mirror their layout in RTL mode. Without dir="rtl", these elements default to LTR positioning, which feels unnatural for RTL language speakers.
- Bidirectional text handling: Documents often contain mixed-direction content (e.g., Arabic text with embedded English words, numbers, or brand names). The Unicode Bidirectional Algorithm (BiDi) uses the base direction set by dir to correctly resolve the ordering of these mixed runs of text.
- Standards compliance: The WHATWG HTML Living Standard recommends that authors set the dir attribute to match the language of the document, and the W3C Internationalization guidelines strongly encourage it for RTL languages.
How to fix it
Add dir="rtl" to your <html> start tag. If you haven’t already, also include the appropriate lang attribute for the specific language you’re using.
For Arabic:
<html dir="rtl" lang="ar">
For Hebrew:
<html dir="rtl" lang="he">
For Persian:
<html dir="rtl" lang="fa">
Examples
❌ Missing dir attribute on an Arabic document
<!DOCTYPE html>
<html lang="ar">
<head>
<meta charset="utf-8">
<title>مرحبا بالعالم</title>
</head>
<body>
<h1>مرحبا بالعالم</h1>
<p>هذه صفحة تجريبية باللغة العربية.</p>
</body>
</html>
The browser will render this page with a left-to-right base direction. The heading and paragraph text may appear left-aligned, and any mixed-direction content (like embedded numbers or English words) may be ordered incorrectly.
✅ Correct: dir="rtl" added to the <html> tag
<!DOCTYPE html>
<html dir="rtl" lang="ar">
<head>
<meta charset="utf-8">
<title>مرحبا بالعالم</title>
</head>
<body>
<h1>مرحبا بالعالم</h1>
<p>هذه صفحة تجريبية باللغة العربية.</p>
</body>
</html>
Now the browser knows to render the page right-to-left. Text will be right-aligned by default, scroll bars will appear on the left, and the overall layout will feel natural for Arabic readers.
Handling mixed-direction content within a page
If your RTL document contains sections in a left-to-right language, use the dir attribute on individual elements to override the base direction locally:
<p>قام المستخدم بزيارة <span dir="ltr">www.example.com</span> اليوم.</p>
This ensures the URL renders in the correct left-to-right order while the surrounding Arabic text flows right-to-left. Setting dir="rtl" on the <html> element establishes the correct default, and you only need per-element overrides for embedded LTR content.
The <source> element is used inside <picture>, <audio>, or <video> elements to specify alternative media resources. When used inside a <picture> element, the srcset attribute is required and must contain one or more comma-separated image candidate strings. Each image candidate string consists of a URL and an optional descriptor — either a width descriptor like 400w or a pixel density descriptor like 2x.
This validation error typically occurs when:
- The srcset attribute is present but empty (srcset="").
- The attribute value contains only whitespace.
- The value is malformed or contains syntax errors (e.g., missing URLs, invalid descriptors).
- A dynamic templating system or CMS outputs the attribute with no value.
Why this matters
Browsers rely on the srcset attribute to select the most appropriate image to display based on the user’s device capabilities, viewport size, and network conditions. An empty or invalid srcset means the browser cannot perform this selection, potentially resulting in no image being displayed at all. This degrades the user experience, harms accessibility (screen readers and assistive technologies may encounter unexpected behavior), and violates the HTML specification as defined by the WHATWG living standard.
How to fix it
- Provide at least one valid image URL in the srcset attribute.
- Optionally add descriptors — use width descriptors (w) when combined with the sizes attribute, or pixel density descriptors (x) for fixed-size images.
- If you have no image to provide, remove the <source> element entirely rather than leaving srcset empty.
- Check dynamic output — if a CMS or templating engine generates the srcset value, add a conditional check to omit the <source> element when no images are available.
Examples
❌ Empty srcset attribute
<picture>
<source srcset="" type="image/webp">
<img src="photo.jpg" alt="A sunset over the ocean">
</picture>
This triggers the error because srcset is present but contains no image candidate strings.
❌ Invalid descriptor syntax
<picture>
<source srcset="photo.webp 400" type="image/webp">
<img src="photo.jpg" alt="A sunset over the ocean">
</picture>
This is invalid because 400 is not a recognized descriptor — it must be 400w or a density descriptor like 2x.
✅ Single image candidate
<picture>
<source srcset="photo.webp" type="image/webp">
<img src="photo.jpg" alt="A sunset over the ocean">
</picture>
A single URL without a descriptor is valid and serves as the default 1x candidate.
✅ Multiple candidates with width descriptors
<picture>
<source
srcset="photo-small.webp 400w, photo-medium.webp 800w, photo-large.webp 1200w"
sizes="(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px"
type="image/webp">
<img src="photo.jpg" alt="A sunset over the ocean">
</picture>
This provides three image candidates with width descriptors, allowing the browser to choose the best match based on the viewport and display density.
✅ Multiple candidates with pixel density descriptors
<picture>
<source srcset="photo.webp 1x, photo-2x.webp 2x" type="image/webp">
<img src="photo.jpg" alt="A sunset over the ocean">
</picture>
Pixel density descriptors tell the browser which image to use based on the device’s pixel ratio — 1x for standard displays and 2x for high-DPI (Retina) screens.
✅ Removing the source element when no image is available
If your application dynamically generates the srcset value and sometimes has no image to provide, omit the <source> element entirely:
<picture>
<img src="photo.jpg" alt="A sunset over the ocean">
</picture>
This is valid because the <img> element inside <picture> serves as the required fallback and can stand alone.
The URL standard (defined by WHATWG) specifies a strict set of characters allowed in each part of a URL. A space character is not among them. When the validator encounters a literal space in an href value, it reports the error “Illegal character in scheme data: space is not allowed.” This applies to spaces anywhere in the URL — the path, query string, fragment, or even after the scheme (e.g., https:).
While most modern browsers are forgiving and will attempt to fix malformed URLs by encoding spaces automatically, relying on this behavior is problematic for several reasons:
- Standards compliance: Invalid URLs violate the HTML specification, and markup that depends on browser error-correction is fragile and unpredictable.
- Accessibility: Assistive technologies, such as screen readers, may not handle malformed URLs the same way browsers do. This can result in broken links for users relying on these tools.
- Interoperability: Non-browser consumers of your HTML — search engine crawlers, link checkers, email clients, RSS readers, and APIs — may not perform the same auto-correction, leading to broken links or missed content.
- Copy-paste and sharing: When users copy a malformed URL from the source, the space can cause the link to break when pasted into other applications.
How to fix it
The fix depends on where the space appears:
- In the path or fragment: Replace each space with %20. For example, /my file.html becomes /my%20file.html.
- In the query string: You can use %20 or, if the value is part of application/x-www-form-urlencoded data, + is also acceptable for spaces within query parameter values. However, %20 is universally safe.
- Programmatically: Use encodeURI() in JavaScript to encode a full URL (it preserves structural characters like /, ?, and #). Use encodeURIComponent() to encode individual query parameter values. On the server side, use your language’s equivalent URL-encoding function.
If you’re writing URLs by hand in HTML, simply find every space and replace it with %20. If URLs are generated dynamically (from a database, CMS, or user input), ensure your templating or server-side code encodes them before inserting into the markup.
Examples
Invalid — space in the path
<a href="https://example.com/docs/My Report.pdf">Download Report</a>
The literal space between “My” and “Report” triggers the validator error.
Fixed — space encoded as %20
<a href="https://example.com/docs/My%20Report.pdf">Download Report</a>
Invalid — space in a query parameter
<a href="https://example.com/search?q=hello world">Search</a>
Fixed — space encoded in the query string
<a href="https://example.com/search?q=hello%20world">Search</a>
Invalid — multiple spaces in different URL parts
<a href="https://example.com/my folder/page two.html?ref=some value#my section">Link</a>
Fixed — all spaces encoded
<a href="https://example.com/my%20folder/page%20two.html?ref=some%20value#my%20section">Link</a>
Encoding URLs with JavaScript
If you’re building URLs dynamically, use the built-in encoding functions rather than doing manual string replacement:
<script>
// encodeURI encodes a full URL but preserves :, /, ?, #, etc.
const url = encodeURI("https://example.com/docs/My Report.pdf");
// Result: "https://example.com/docs/My%20Report.pdf"
// encodeURIComponent encodes a single value (for query params)
const query = encodeURIComponent("hello world");
// Result: "hello%20world"
</script>
Note that encodeURI() is appropriate for encoding a complete URL, while encodeURIComponent() should be used for individual components like query parameter values — it encodes characters such as / and ? that have structural meaning in a URL.
The <time> HTML element represents a specific period in time or a duration. Its datetime attribute translates human-readable text into a machine-readable format, enabling browsers, search engines, and assistive technologies to reliably parse and understand temporal data. When the datetime value doesn’t match one of the accepted formats, the machine-readable purpose of the element is defeated — tools cannot interpret the date or time, which undermines features like search engine rich results, calendar integration, and accessibility enhancements for screen readers.
The HTML specification defines several valid formats for the datetime attribute. Here are the most commonly used ones:
| Format | Example | Description |
|---|---|---|
| Date | 2024-03-15 | Year, month, day |
| Month | 2024-03 | Year and month only |
| Year | 2024 | Valid year |
| Yearless date | 03-15 | Month and day without year |
| Time | 14:30 or 14:30:00 | Hours and minutes (seconds optional) |
| Date and time | 2024-03-15T14:30 | Date and time separated by T |
| Date and time with timezone | 2024-03-15T14:30Z or 2024-03-15T14:30+05:30 | With UTC (Z) or offset |
| Duration (precise) | PT1H30M | ISO 8601 duration |
| Duration (approximate) | P2Y6M | Years, months, etc. |
| Week | 2024-W12 | ISO week number |
Common mistakes that trigger this error include:
- Using slashes instead of hyphens: 03/15/2024 instead of 2024-03-15
- Using informal date formats: March 15, 2024 or 15-03-2024
- Omitting the T separator between date and time: 2024-03-15 14:30
- Using 12-hour time with AM/PM: 2:30 PM instead of 14:30
- Providing incomplete values: 2024-3-5 instead of 2024-03-05 (months and days must be zero-padded)
Examples
Invalid: Slash-separated date
<time datetime="03/15/2024">March 15, 2024</time>
Valid: ISO 8601 date format
<time datetime="2024-03-15">March 15, 2024</time>
Invalid: Missing T separator and using AM/PM
<time datetime="2024-03-15 2:30 PM">March 15 at 2:30 PM</time>
Valid: Date-time with T separator and 24-hour time
<time datetime="2024-03-15T14:30">March 15 at 2:30 PM</time>
Invalid: Informal time string
<time datetime="half past two">2:30 PM</time>
Valid: Simple time value
<time datetime="14:30">2:30 PM</time>
Invalid: Non-standard duration
<time datetime="1 hour 30 minutes">1.5 hours</time>
Valid: ISO 8601 duration
<time datetime="PT1H30M">1.5 hours</time>
Valid: Date-time with timezone offset
<p>The event starts at <time datetime="2024-03-15T14:30-05:00">2:30 PM ET on March 15</time>.</p>
Valid: Using only the month
<p>Published in <time datetime="2024-03">March 2024</time>.</p>
Remember that the human-readable text content between the <time> tags can be in any format you like — it’s only the datetime attribute value that must follow the specification. This lets you display dates in a user-friendly way while still providing a standardized machine-readable value.
The target attribute on the <area> element tells the browser where to display the linked resource — in the current tab, a new tab, a parent frame, or a named <iframe>. According to the WHATWG HTML living standard, a valid navigable target must be either a keyword beginning with an underscore (_self, _blank, _parent, _top) or a name that is at least one character long. An empty string ("") satisfies neither condition, so the W3C validator reports:
Bad value “” for attribute “target” on element “area”: Browsing context name must be at least one character long.
This commonly happens when templating engines or CMS platforms output target="" as a default, or when a value is conditionally set but the logic fails to produce a result.
Why this matters
- Standards compliance. An empty target violates the HTML specification and produces a validation error.
- Unpredictable browser behavior. While most browsers treat an empty target the same as _self, this is not guaranteed by the spec. Relying on undefined behavior can lead to inconsistencies across browsers or future versions.
- Code clarity. An empty target signals unclear intent. Removing it or using an explicit keyword makes the code easier to understand and maintain.
How to fix it
- Remove the target attribute if you want the link to open in the same browsing context. This is the default behavior, equivalent to target="_self".
- Use a valid keyword like _self, _blank, _parent, or _top if you need specific navigation behavior.
- Use a named browsing context (e.g., target="contentFrame") if you want to direct the link to a specific <iframe> or window. The name must be at least one character long.
- Fix your template logic if the empty value is being generated dynamically. Ensure the target attribute is only rendered when a non-empty value is available.
Examples
Invalid: empty target attribute
This triggers the validation error because target is set to an empty string:
<img src="floor-plan.png" usemap="#rooms" alt="Floor plan">
<map name="rooms">
<area shape="rect" coords="10,10,100,60" href="/kitchen" alt="Kitchen" target="">
</map>
Fixed: remove target for default behavior
If you want the link to open in the same tab (the default), simply remove the target attribute:
<img src="floor-plan.png" usemap="#rooms" alt="Floor plan">
<map name="rooms">
<area shape="rect" coords="10,10,100,60" href="/kitchen" alt="Kitchen">
</map>
Fixed: use a valid keyword
Use _self to be explicit about same-tab navigation, or _blank to open in a new tab:
<img src="floor-plan.png" usemap="#rooms" alt="Floor plan">
<map name="rooms">
<area shape="rect" coords="10,10,100,60" href="/kitchen" alt="Kitchen" target="_self">
<area shape="rect" coords="110,10,200,60" href="/bedroom" alt="Bedroom" target="_blank">
</map>
Fixed: target a named <iframe>
If you want to load the linked resource into a specific <iframe>, give the <iframe> a name attribute and reference it in target:
<iframe name="detailView" src="about:blank" title="Room details"></iframe>
<img src="floor-plan.png" usemap="#rooms" alt="Floor plan">
<map name="rooms">
<area shape="rect" coords="10,10,100,60" href="/kitchen" alt="Kitchen" target="detailView">
</map>
Fixed: conditionally render target in templates
If your target value comes from a variable, make sure the attribute is only output when the value is non-empty. For example, in a Jinja-style template:
<area shape="rect" coords="10,10,100,60" href="/kitchen" alt="Kitchen"
{% if target_value %} target="{{ target_value }}"{% endif %}>
This prevents target="" from appearing in your HTML when no value is set.
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.
URLs follow strict syntax rules defined by RFC 3986, which does not allow literal space characters in any part of a URL — whether in the path, query string, or fragment. While many browsers will silently handle spaces by encoding them before making a request, the raw HTML is still invalid. The W3C HTML validator flags this because the src attribute expects a valid URL, and a URL containing a raw space does not conform to the standard.
This issue commonly appears in two scenarios: spaces in file paths (e.g., my image.jpg) and spaces in query string values (e.g., ?search=my term). Both must be percent-encoded. The percent-encoded form of a space is %20. In query strings specifically, you may also see + used to represent spaces (as defined by application/x-www-form-urlencoded), which is also valid in that context.
Beyond standards compliance, raw spaces in URLs can cause real problems. Some older browsers or HTTP clients may truncate the URL at the first space, leading to broken images or failed resource loads. Spaces can also cause issues with link sharing, copy-pasting, and server-side URL parsing. Proper encoding ensures your URLs work reliably across all environments.
How to fix it
- Replace spaces with %20 in all parts of the URL. This is the universally safe approach.
- Rename files to avoid spaces altogether. Use hyphens (-) or underscores (_) instead of spaces in file and directory names.
- Use + in query strings if you prefer, though %20 works everywhere in a URL.
If you’re generating URLs programmatically, use built-in encoding functions like JavaScript’s encodeURI() or encodeURIComponent() to handle this automatically.
Examples
Spaces in the file path
This triggers the validation error because the file name contains a space:
<!-- ❌ Invalid: space in path segment -->
<img src="/images/my photo.jpg" alt="A vacation photo">
Fix it by encoding the space:
<!-- ✅ Valid: space encoded as %20 -->
<img src="/images/my%20photo.jpg" alt="A vacation photo">
Or better yet, rename the file to avoid spaces:
<!-- ✅ Valid: no spaces in file name -->
<img src="/images/my-photo.jpg" alt="A vacation photo">
Spaces in the query string
This triggers the error because the query parameter value contains a space:
<!-- ❌ Invalid: space in query string -->
<img src="https://example.com/image?title=sunset beach" alt="Sunset at the beach">
Fix by percent-encoding the space:
<!-- ✅ Valid: space encoded as %20 -->
<img src="https://example.com/image?title=sunset%20beach" alt="Sunset at the beach">
Using + is also acceptable in query strings:
<!-- ✅ Valid: space encoded as + in query string -->
<img src="https://example.com/image?title=sunset+beach" alt="Sunset at the beach">
Multiple spaces in a URL
When a URL has multiple spaces, each one must be encoded:
<!-- ❌ Invalid: multiple spaces -->
<img src="/uploads/user photos/trip to paris.jpg" alt="Trip to Paris">
<!-- ✅ Valid: all spaces encoded -->
<img src="/uploads/user%20photos/trip%20to%20paris.jpg" alt="Trip to Paris">
The target attribute specifies where a linked document should be opened. When the validator encounters target="", it reports an error because the HTML specification requires browsing context names to be at least one character long. An empty string is not a valid browsing context name and has no defined behavior, which means browsers may handle it inconsistently.
This issue commonly arises when a target value is dynamically generated by a CMS, template engine, or JavaScript and the value ends up being empty. It can also happen when a developer adds the attribute with the intent to fill it in later but forgets to do so.
Setting target to an empty string is problematic for several reasons:
- Standards compliance: The WHATWG HTML specification explicitly requires valid browsing context names to be non-empty strings. An empty value violates this rule.
- Unpredictable behavior: Browsers may interpret an empty target differently — some may treat it like _self, others may behave unexpectedly. This makes your site harder to test and maintain.
- Accessibility concerns: Screen readers and assistive technologies may announce the target attribute or use it to inform users about link behavior. An empty value provides no meaningful information.
To fix this, choose one of the following approaches:
- Remove the attribute if you want the default behavior (opening in the same browsing context, equivalent to _self).
- Set a valid keyword like _blank, _self, _parent, or _top.
- Set a custom name if you want multiple links to share the same browsing context (e.g., target="externalWindow").
Examples
❌ Invalid: empty target attribute
<a href="https://example.com" target="">Visit Example</a>
This triggers the validation error because the target value is an empty string.
✅ Fixed: remove target entirely
If you want the link to open in the current browsing context (the default), simply remove the attribute:
<a href="https://example.com">Visit Example</a>
✅ Fixed: use _blank to open in a new tab
<a href="https://example.com" target="_blank" rel="noopener">Visit Example</a>
Note the addition of rel="noopener" — this is a security best practice when using target="_blank", as it prevents the opened page from accessing the window.opener property.
✅ Fixed: use _self explicitly
If you want to be explicit about opening in the same context:
<a href="https://example.com" target="_self">Visit Example</a>
✅ Fixed: use a custom browsing context name
You can use a custom name so that multiple links reuse the same tab or window:
<a href="https://example.com" target="externalWindow">Example</a>
<a href="https://example.org" target="externalWindow">Example Org</a>
Both links will open in the same browsing context named externalWindow. If it doesn’t exist yet, the browser creates it; subsequent clicks reuse it.
Dynamic templates
If your target value comes from a template or CMS, make sure the attribute is conditionally rendered rather than output with an empty value. For example, in a templating language:
<!-- Instead of always outputting target -->
<a href="https://example.com" target="">Visit</a>
<!-- Only include target when a value exists -->
<a href="https://example.com" target="_blank" rel="noopener">Visit</a>
In your template logic, conditionally omit the target attribute entirely when no value is provided, rather than rendering it as an empty string.
A mailto: link follows URI syntax as defined by RFC 3986, which does not permit raw space characters anywhere in the URI. When the W3C validator encounters a space inside the href value of a mailto: link, it reports it as an illegal character in the scheme data. This most commonly happens due to a typo in the email address itself — for example, accidentally inserting a space in the domain name (example .com) or the local part (user name@example.com). It can also occur when query parameters like subject or body contain unencoded spaces.
This matters for several reasons. First, browsers may truncate or misinterpret the href at the space boundary, meaning the mail client may open with an incorrect or incomplete email address. Second, assistive technologies rely on well-formed URIs to communicate link destinations to users. A malformed mailto: link can confuse screen readers or prevent users from understanding where the link leads. Third, invalid markup signals poor quality to search engines and automated tools.
To fix this issue:
- Check the email address for typos. Remove any accidental spaces in the local part (before @) or the domain part (after @).
- Percent-encode spaces in query parameters. If you’re using subject, body, or cc parameters in the mailto: URI, replace spaces with %20.
- Avoid copying and pasting email addresses from formatted documents, which can introduce non-breaking spaces or other invisible whitespace characters.
Examples
Invalid — space in the email address
A space in the domain name makes the URI invalid:
<a href="mailto:user@example com">Send Email</a>
Valid — corrected email address
Remove the space to form a valid email address:
<a href="mailto:user@example.com">Send Email</a>
Invalid — space in the local part
<a href="mailto:john doe@example.com">Send Email</a>
Valid — space removed from local part
<a href="mailto:johndoe@example.com">Send Email</a>
Invalid — unencoded spaces in subject parameter
<a href="mailto:info@example.com?subject=Hello World">Email Us</a>
Valid — percent-encoded spaces in subject parameter
Replace each space with %20 in query parameter values:
<a href="mailto:info@example.com?subject=Hello%20World">Email Us</a>
Valid — full mailto with multiple parameters
<a href="mailto:support@example.com?subject=Bug%20Report&body=Please%20describe%20the%20issue.">
Report a Bug
</a>
HTML elements follow strict nesting rules defined by the WHATWG HTML Living Standard. Every element has a content model — a description of what content (text, elements, or both) it may contain. When you place an element somewhere it isn’t allowed, the browser must guess your intent and may restructure the DOM in unexpected ways. This can lead to inconsistent rendering across browsers, broken layouts, and accessibility issues for screen readers and other assistive technologies.
The “(Suppressing further errors from this subtree.)” part of the message means the validator has stopped checking anything nested inside the problematic element. This is important — it means there could be additional errors hidden beneath this one. Fixing this nesting issue may reveal further problems that need attention.
Here are some of the most common cases that trigger this error:
- Block elements inside inline elements: Placing a <div> inside a <span> or an <a> that doesn’t permit flow content in that context.
- Invalid list children: Putting <div>, <p>, or other elements directly inside <ul> or <ol>, which only allow <li> (and <script>/<template>) as direct children.
- Invalid table structure: Placing <td> directly inside <table> without wrapping it in <tr>, or putting non-table elements where only <thead>, <tbody>, <tfoot>, or <tr> are expected.
- Headings or paragraphs inside <p>: The <p> element only accepts phrasing content, so nesting a <h2> or another <p> inside it is invalid.
- Interactive elements inside interactive elements: Nesting a <button> inside an <a>, or an <a> inside a <button>.
To fix the issue, consult the MDN documentation for the parent element and check its Permitted content section. Then either move the child element to a valid location, wrap it in an appropriate intermediary element, or replace the parent or child with a more suitable element.
Examples
Invalid list children
A <ul> only permits <li> elements as direct children.
❌ Incorrect:
<ul>
<div>
<li>Item one</li>
<li>Item two</li>
</div>
</ul>
✅ Correct:
<ul>
<li>Item one</li>
<li>Item two</li>
</ul>
Block element inside a paragraph
The <p> element only accepts phrasing content. A <div> is flow content and cannot be nested inside it.
❌ Incorrect:
<p>
Here is some text.
<div class="highlight">This is highlighted.</div>
</p>
✅ Correct:
<p>Here is some text.</p>
<div class="highlight">This is highlighted.</div>
Or use a <span> if you want inline styling within the paragraph:
<p>
Here is some text.
<span class="highlight">This is highlighted.</span>
</p>
Invalid table structure
Table cells (<td>) must be inside a <tr>, which itself must be inside <thead>, <tbody>, <tfoot>, or directly inside <table>.
❌ Incorrect:
<table>
<td>Name</td>
<td>Age</td>
</table>
✅ Correct:
<table>
<tr>
<td>Name</td>
<td>Age</td>
</tr>
</table>
Interactive elements nested inside interactive elements
A <button> cannot be placed inside an <a> element, and vice versa, because interactive content cannot nest inside other interactive content.
❌ Incorrect:
<a href="/dashboard">
<button>Go to Dashboard</button>
</a>
✅ Correct (choose one or the other):
<a href="/dashboard">Go to Dashboard</a>
Or style a link to look like a button using CSS:
<a href="/dashboard" class="button">Go to Dashboard</a>
Wrapping elements inside <select>
The <select> element only allows <option>, <optgroup>, and scripting elements as children.
❌ Incorrect:
<select>
<div>
<option>Apple</option>
<option>Banana</option>
</div>
</select>
✅ Correct:
<select>
<optgroup label="Fruits">
<option>Apple</option>
<option>Banana</option>
</optgroup>
</select>
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 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 HTML specification defines a strict content model for the <ul> (unordered list) element: its permitted content is zero or more <li> elements, optionally intermixed with <script> and <template> elements. A <div> is not among these allowed children, so placing one directly inside a <ul> produces a validation error.
This issue commonly arises when developers try to group or wrap list items for styling or layout purposes. For example, you might want to add a container around certain <li> elements for flexbox or grid alignment, or you might be using a templating system that injects wrapper <div> elements into your list markup.
Why this matters
- Browser parsing inconsistencies: When browsers encounter invalid nesting, they attempt to fix the DOM structure automatically, but different browsers may handle it differently. This can lead to unexpected rendering where list items appear outside their intended container or styles break unpredictably.
- Accessibility: Screen readers and assistive technologies rely on correct semantic structure to convey list relationships to users. A <div> breaking the <ul> → <li> relationship can cause assistive tools to misinterpret or skip list content entirely.
- Standards compliance: Invalid HTML can cause cascading parser errors — note that the validator message says “Suppressing further errors from this subtree,” meaning additional issues within that structure may be hidden from you.
How to fix it
- Move the <div> inside the <li>: If you need a wrapper for styling, place it inside the list item rather than around it.
- Remove the <div> entirely: If it serves no purpose, simply remove it and let the <li> elements sit directly inside the <ul>.
- Use CSS on the <li> elements: In many cases, you can apply the styles you need directly to the <li> elements without an extra wrapper.
- Use role="list" on a <div> parent: If your layout truly requires <div> wrappers, consider restructuring with ARIA roles, though native semantic elements are always preferred.
Examples
❌ Incorrect: <div> as a direct child of <ul>
<ul>
<div>
<li>Apples</li>
<li>Bananas</li>
</div>
<div>
<li>Carrots</li>
<li>Dates</li>
</div>
</ul>
✅ Correct: Move the <div> inside each <li>
<ul>
<li><div>Apples</div></li>
<li><div>Bananas</div></li>
<li><div>Carrots</div></li>
<li><div>Dates</div></li>
</ul>
✅ Correct: Remove the <div> entirely
<ul>
<li>Apples</li>
<li>Bananas</li>
<li>Carrots</li>
<li>Dates</li>
</ul>
❌ Incorrect: Using a <div> as a styling wrapper around list items
<ul class="product-list">
<div class="row">
<li>Product A</li>
<li>Product B</li>
<li>Product C</li>
</div>
</ul>
✅ Correct: Apply layout styles directly to the <ul> and <li> elements
<ul class="product-list row">
<li>Product A</li>
<li>Product B</li>
<li>Product C</li>
</ul>
❌ Incorrect: Template or component wrapper inserting a <div>
This often happens in frameworks where a component renders a wrapping <div>:
<ul>
<div class="list-item-wrapper">
<li>Item 1</li>
</div>
<div class="list-item-wrapper">
<li>Item 2</li>
</div>
</ul>
✅ Correct: Move the wrapper class to the <li> itself
<ul>
<li class="list-item-wrapper">Item 1</li>
<li class="list-item-wrapper">Item 2</li>
</ul>
The same rule applies to <ol> (ordered list) elements — they share the same content model restriction. Always ensure that <li> is the direct child of <ul> or <ol>, and place any additional wrapper elements inside the <li> rather than around it.
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>
URLs follow strict syntax rules defined by RFC 3986, which does not allow literal space characters. When the W3C validator encounters a space in the src attribute of an <img> element, it reports it as an illegal character in the scheme data. While most modern browsers will attempt to handle spaces in URLs by automatically encoding them, relying on this behavior is not standards-compliant and can lead to broken images in certain contexts, such as when URLs are processed by other tools, APIs, or older browsers.
This issue commonly arises when file names contain spaces (e.g., my photo.jpg) and the path is copied directly into the HTML. It can also happen when a URL is incorrectly constructed by concatenating strings with unencoded values.
Beyond validation, unencoded spaces can cause real problems. A URL with a space may break when shared, copied, or passed through redirects. Email clients and messaging apps may truncate the URL at the space. Search engine crawlers might fail to index the resource correctly.
How to fix it
There are several ways to resolve this issue:
- Percent-encode spaces as %20 — Replace every literal space in the URL with %20.
- Rename the file — Remove spaces from file names entirely, using hyphens (-), underscores (_), or camelCase instead.
- Use proper URL encoding functions — If generating URLs dynamically (e.g., in JavaScript or a server-side language), use built-in encoding functions like encodeURI() or encodeURIComponent().
Renaming files to avoid spaces is generally the best long-term practice, as it prevents encoding issues across your entire workflow.
Examples
❌ Invalid: spaces in the src attribute
<img src="images/my photo.jpg" alt="A vacation photo">
<img src="/assets/blog posts/header image.png" alt="Blog header">
Both of these will trigger the validation error because the src value contains literal space characters.
✅ Fixed: spaces encoded as %20
<img src="images/my%20photo.jpg" alt="A vacation photo">
<img src="/assets/blog%20posts/header%20image.png" alt="Blog header">
✅ Fixed: file renamed to remove spaces
<img src="images/my-photo.jpg" alt="A vacation photo">
<img src="/assets/blog-posts/header-image.png" alt="Blog header">
A note on + vs %20
You may see spaces encoded as + in some contexts (e.g., query strings in form submissions using application/x-www-form-urlencoded). However, + is not a valid substitute for a space in a URL path. Always use %20 for spaces in the src attribute.
<!-- ❌ Incorrect: + does not represent a space in a URL path -->
<img src="images/my+photo.jpg" alt="A vacation photo">
<!-- ✅ Correct -->
<img src="images/my%20photo.jpg" alt="A vacation photo">
When you write a phone link using <a href="callto:...">, you may encounter two distinct problems at once. First, the callto: scheme is a legacy, non-standard protocol originally associated with Skype. The correct and widely supported URI scheme for telephone links is tel:, as defined by RFC 3966. Second, spaces within URI scheme data are illegal characters. URIs must not contain unencoded spaces anywhere, and telephone URIs specifically expect a compact phone number composed of digits, hyphens (-), dots (.), and an optional leading plus sign (+) for international dialing.
The W3C validator raises this error because the value provided to href violates URI syntax rules. Browsers may still attempt to handle the link, but behavior will be inconsistent — some mobile browsers may not recognize callto: at all, and spaces in the URI can cause the number to be parsed incorrectly or truncated. Using the standard tel: scheme with a properly formatted number ensures the link works reliably across devices and platforms, including mobile phones, VoIP applications, and assistive technologies.
How to fix it
- Replace callto: with tel: — The tel: scheme is the standard for phone number links and is supported by all modern browsers and mobile operating systems.
- Remove spaces and slashes — Strip out any spaces, slashes, or parentheses from the phone number. These characters are not valid in a tel: URI without percent-encoding, and they serve no functional purpose in the link target.
- Use a leading + for international numbers — If applicable, include the full international dialing code prefixed with + (e.g., +1 for the US, +49 for Germany). This makes the link work regardless of the caller’s location.
- Optional visual separators — If you want visual separators within the href for readability in your source code, use hyphens (-) or dots (.), which are permitted in tel: URIs. However, the simplest and safest approach is digits only (plus the optional leading +).
Examples
Incorrect: callto: with spaces and slashes
This triggers the validator error because spaces and slashes are illegal in URI scheme data, and callto: is non-standard.
<a href="callto:07142/ 12 34 5">Call us</a>
Incorrect: tel: with spaces
Even with the correct tel: scheme, spaces in the phone number are still invalid URI characters.
<a href="tel:07142 12 34 5">Call us</a>
Correct: tel: with digits only
<a href="tel:0714212345">Call us</a>
Correct: International number with + prefix
<a href="tel:+490714212345">Call us</a>
Correct: Using hyphens for readability
Hyphens are valid characters in tel: URIs and can improve source code readability without affecting functionality.
<a href="tel:+49-07142-12345">Call us</a>
Displaying a formatted number to the user
You can still show a human-friendly formatted number as the visible link text while keeping the href value clean and valid.
<a href="tel:+490714212345">+49 (0) 7142 / 12 34 5</a>
This approach gives you the best of both worlds: the link text is easy for users to read, and the href value is a valid, standards-compliant tel: URI that works reliably across all devices and passes W3C validation.
The step attribute specifies the granularity that an input’s value must adhere to. It controls the increment when a user clicks the up/down spinner buttons on a number input, moves a slider on a range input, or adjusts date and time inputs. The browser also uses this value during constraint validation to determine which values are considered valid based on the stepping base (typically the min value or the input’s default).
When the HTML specification defines the step attribute, it requires the value to be a valid floating-point number greater than zero. A value of "0" is explicitly invalid because a step of zero means no increment at all — it’s logically meaningless. You can’t step through values in increments of nothing. The W3C validator flags this as an error because "0" fails the requirement of being a positive number.
Why this matters
- Standards compliance: The WHATWG HTML living standard explicitly requires step to parse as a number greater than zero. A value of "0" violates this rule.
- Browser behavior: While browsers may not crash on step="0", the behavior becomes unpredictable. Spinner buttons may stop working, and form validation may not function as expected.
- Accessibility: Assistive technologies rely on correct step values to communicate valid input ranges to users. An invalid step can lead to a confusing experience.
How to fix it
Choose the appropriate fix depending on your intent:
- If you want specific increments (e.g., whole numbers, cents, tenths), set step to the desired interval like "1", "0.01", or "0.1".
- If you want to allow any value with no stepping constraint, use the special keyword step="any". This tells the browser that no stepping is implied and any floating-point value is acceptable.
- If you don’t need the attribute at all, simply remove it. Each input type has a default step value (e.g., 1 for type="number").
Examples
❌ Invalid: step set to zero
<label for="price">Price:</label>
<input id="price" name="price" type="number" step="0" min="0">
This triggers the validation error because "0" is not a valid positive floating-point number.
✅ Fixed: using a specific step value
If you want the input to accept values in increments of one cent:
<label for="price">Price:</label>
<input id="price" name="price" type="number" step="0.01" min="0">
Valid values would include 0, 0.01, 0.02, 1.50, 99.99, and so on.
✅ Fixed: using step="any" for unrestricted precision
If you want to allow any numeric value without stepping constraints:
<label for="price">Price:</label>
<input id="price" name="price" type="number" step="any" min="0">
This permits any floating-point value, such as 3.14159 or 0.007.
✅ Fixed: using a whole number step with a non-zero minimum
<label for="quantity">Quantity:</label>
<input id="quantity" name="quantity" type="number" step="2" min="1.3">
With step="2" and min="1.3", valid values include 1.3, 3.3, 5.3, 7.3, and so on. The stepping base is 1.3, and each valid value is an even multiple of 2 away from it.
✅ Fixed: removing the attribute entirely
If the default step behavior is sufficient, simply omit the attribute:
<label for="amount">Amount:</label>
<input id="amount" name="amount" type="number" min="0">
The default step for type="number" is 1, so valid values are whole numbers (0, 1, 2, etc.).
URLs follow strict syntax rules defined by RFC 3986, which does not permit literal space characters anywhere in the URI — including path segments, query strings, and fragment identifiers. When the W3C HTML Validator encounters a space in the src attribute of a <script> element, it flags it as an illegal character because the attribute value is not a valid URL.
While most modern browsers will silently fix this by encoding the space before making the request, relying on this behavior is problematic for several reasons:
- Standards compliance: The HTML specification requires that the src attribute contain a valid URL. A URL with a literal space is technically malformed.
- Cross-browser reliability: Not all user agents, proxies, or CDNs handle malformed URLs the same way. What works in one browser may fail in another context.
- Interoperability: Other tools that consume your HTML — such as linters, crawlers, screen readers, and build pipelines — may not be as forgiving as browsers.
- Copy-paste and linking issues: Literal spaces in URLs cause problems when users copy links or when URLs appear in plain-text contexts like emails, where the space may break the URL in two.
How to fix it
You have three options, listed from most recommended to least:
- Rename the file or directory to eliminate spaces entirely (e.g., use hyphens or underscores). This is the cleanest solution.
- Percent-encode the space as %20 in the src attribute value.
- Use a build tool or bundler that generates references with properly encoded or space-free paths automatically.
Avoid using + as a space replacement in path segments. The + character represents a space only in application/x-www-form-urlencoded query strings, not in URL path segments.
Examples
❌ Invalid: space in the path segment
<script src="https://example.com/media assets/app.js"></script>
The space between media and assets makes this an invalid URL.
✅ Fixed: percent-encode the space
<script src="https://example.com/media%20assets/app.js"></script>
Replacing the space with %20 produces a valid, standards-compliant URL.
✅ Better: rename to avoid spaces entirely
<script src="https://example.com/media-assets/app.js"></script>
Using a hyphen (or underscore) instead of a space is the preferred approach. It keeps URLs clean, readable, and free of encoding issues.
❌ Invalid: space in a local relative path
This issue isn’t limited to absolute URLs. Relative paths trigger the same error:
<script src="js/my script.js"></script>
✅ Fixed: encode or rename the local file
<script src="js/my%20script.js"></script>
Or, better yet:
<script src="js/my-script.js"></script>
Multiple spaces and other special characters
If a URL contains multiple spaces or other special characters, each one must be individually encoded. For example, { becomes %7B and } becomes %7D. A quick reference for common characters:
| Character | Encoded form |
|---|---|
| Space | %20 |
| [ | %5B |
| ] | %5D |
| { | %7B |
| } | %7D |
<!-- Invalid -->
<script src="libs/my library [v2].js"></script>
<!-- Valid -->
<script src="libs/my%20library%20%5Bv2%5D.js"></script>
<!-- Best: rename the file -->
<script src="libs/my-library-v2.js"></script>
Note that this same rule applies to the src attribute on other elements like <img>, <iframe>, <audio>, and <video>, as well as the href attribute on <a> and <link>. Whenever you reference a URL in HTML, make sure it contains no literal spaces.
The poster attribute specifies an image to display as a placeholder while the video is loading or before the user starts playback. Like all HTML attributes that accept URLs — such as src, href, and action — the value must conform to valid URI syntax as defined by RFC 3986. In this standard, a literal space character is not a legal character in any part of a URL. When the validator encounters a space in the poster attribute’s value, it flags it as an illegal character in the path segment.
While most modern browsers are forgiving and will attempt to resolve URLs containing raw spaces by internally encoding them, relying on this behavior is problematic for several reasons:
- Standards compliance: The HTML specification requires valid URLs. Raw spaces violate this requirement.
- Interoperability: Not all user agents, HTTP clients, or content delivery systems handle unencoded spaces the same way. Some may truncate the URL at the first space or fail to resolve the resource entirely.
- Portability: If your HTML is consumed by tools, scrapers, or APIs that strictly parse URLs, unencoded spaces can cause silent failures.
- Consistency: Keeping URLs properly encoded prevents subtle bugs when paths are constructed dynamically in server-side or client-side code.
The fix is straightforward. You have two options:
- Percent-encode the spaces: Replace every space in the URL with %20. This preserves the original file and folder names on the server while producing a valid URL in your HTML.
- Eliminate spaces from file and folder names: Use hyphens (-), underscores (_), or camelCase instead of spaces. This is generally considered best practice for web assets, as it avoids encoding issues across the board.
Note that this rule applies to the entire URL path, not just the filename. If any directory in the path contains a space, it must also be encoded or renamed. The same principle applies to other special characters that are reserved or disallowed in URLs, such as {, }, |, ^, and [.
Examples
Incorrect — space in the path
The folder name video images contains a space, which is illegal in a URL path segment.
<video controls poster="/img/video images/snapshot.png">
<source src="/videos/sample.mp4" type="video/mp4">
</video>
Incorrect — space in the filename
The filename my poster.jpg also triggers the same error.
<video controls poster="/img/my poster.jpg">
<source src="/videos/sample.mp4" type="video/mp4">
</video>
Fixed — percent-encoding the spaces
Each space is replaced with %20, producing a valid URL.
<video controls poster="/img/video%20images/snapshot.png">
<source src="/videos/sample.mp4" type="video/mp4">
</video>
Fixed — removing spaces from the path
Renaming the folder to use a hyphen eliminates the need for encoding entirely.
<video controls poster="/img/video-images/snapshot.png">
<source src="/videos/sample.mp4" type="video/mp4">
</video>
Fixed — removing spaces from the filename
<video controls poster="/img/my-poster.jpg">
<source src="/videos/sample.mp4" type="video/mp4">
</video>
As a general best practice, avoid spaces in all file and folder names used on the web. Use hyphens or underscores instead. If you’re working with files you can’t rename — such as assets from a CMS or third-party system — always percent-encode spaces as %20 in your HTML. This applies not only to poster but to every attribute that takes a URL value, including src, href, action, data, and formaction.
Ready to validate your sites?
Start your free trial today.