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.
A link element's href attribute contains a character that is not valid in a URL, such as a space, curly brace, or other unencoded special character.
URLs in HTML must conform to the URL Living Standard. Certain characters are not allowed to appear literally in a URL and must be percent-encoded. Common offenders include spaces (use %20), curly braces { } (use %7B %7D), pipe | (use %7C), and angle brackets < > (use %3C %3E).
This error often appears when a template placeholder like {{variable}} is left unresolved in the href value, or when a URL is copied from another context and contains unencoded characters. The validator reads the raw HTML source, so even if a browser might handle a malformed URL gracefully, the markup is still invalid.
To fix it, replace every illegal character with its percent-encoded equivalent, or remove the character if it does not belong in the URL.
Examples
Invalid: unencoded characters in href
<linkrel="stylesheet"href="https://example.com/styles/main file.css">
The space between main and file is not allowed in a URL.
Valid: percent-encoded URL
<linkrel="stylesheet"href="https://example.com/styles/main%20file.css">
If the illegal characters come from a template placeholder that was never processed, remove the placeholder or ensure it resolves before the HTML is served:
Invalid: unresolved template syntax
<linkrel="icon"href="https://example.com/{{icon_path}}">
Valid: resolved or corrected URL
<linkrel="icon"href="https://example.com/images/favicon.ico">
The <icon> element does not exist in HTML. No version of the HTML specification defines it, and browsers do not recognize it.
This error appears when markup includes <icon> as if it were a standard HTML element. It is not. Browsers will treat it as an unknown inline element with no default behavior or semantics. To display icons, use an <img> element, an <svg> element, or a <span> with CSS-applied background images or icon fonts.
If the intent is to define a favicon (the small icon shown in browser tabs), the correct approach is a <link> element inside <head> with rel="icon".
HTML examples
Invalid: using the <icon> element
<head>
<title>My page</title>
<iconsrc="favicon.png"></icon>
</head>
Valid: using a <link> element for a favicon
<head>
<title>My page</title>
<linkrel="icon"href="favicon.png"type="image/png">
</head>
Valid: displaying an icon inline with <img>
<p>
<imgsrc="star.svg"alt="Star"width="16"height="16"> Favorite
</p>
Valid: displaying an icon inline with <span> and CSS
<p>
<spanclass="icon icon-star"aria-hidden="true"></span> Favorite
</p>
A tel: URI in an href attribute must not contain spaces. The phone number should be written as a continuous string of digits, optionally prefixed with + for the country code.
The tel: URI scheme is defined in RFC 3966. It allows digits, hyphens, dots, and parentheses as visual separators, but spaces are not permitted. Browsers may still dial the number if spaces are present, but the markup is technically invalid.
A common approach is to use the full international number with no spaces in the href value while keeping a human-readable format in the visible link text. If you want visual separators in the href, use hyphens or dots instead of spaces.
Invalid example
<ahref="tel:+1 555 123 4567">Call us</a>
Valid examples
<ahref="tel:+15551234567">+1 555 123 4567</a>
<ahref="tel:+1-555-123-4567">+1 555 123 4567</a>
Square brackets ([ and ]) are not valid characters in a URL path and must be percent-encoded.
The HTML specification requires that the href attribute on an <a> element contains a valid URL. According to RFC 3986, square brackets are reserved characters that are only permitted in the host component of a URI (specifically for IPv6 addresses like [::1]). When they appear in the path, query, or fragment components, they must be percent-encoded as %5B for [ and %5D for ].
This commonly happens with URLs that contain array-like query parameters (e.g., filter[status]=active) or paths that include brackets for some framework-specific routing convention.
To fix the issue, replace every [ with %5B and every ] with %5D in the URL path or query string.
HTML examples
Invalid: square brackets in the URL
<ahref="/products?filter[color]=red&filter[size]=large">
Red, large products
</a>
Valid: percent-encoded square brackets
<ahref="/products?filter%5Bcolor%5D=red&filter%5Bsize%5D=large">
Red, large products
</a>
The encoded version is functionally equivalent. Most web servers and frameworks will decode %5B and %5D back to [ and ] automatically when processing the request.
A < character in an href attribute value is not allowed because it is a reserved character in URLs and must be percent-encoded.
The href attribute on an <a> element must contain a valid URL or URL fragment. Characters like <, >, {, }, and others are not permitted directly in URLs according to RFC 3986. The < character is especially problematic because browsers and parsers interpret it as the start of an HTML tag, which can break your markup and introduce security risks like XSS vulnerabilities.
This error commonly appears when template syntax (e.g., {{variable}}), unescaped user input, or malformed URLs end up inside an href value. If you genuinely need a < in a URL, it must be percent-encoded as %3C. Similarly, > should be encoded as %3E.
Example with the issue
<ahref="https://example.com/search?q=<value>">Search</a>
How to fix it
Percent-encode the special characters in the URL:
<ahref="https://example.com/search?q=%3Cvalue%3E">Search</a>
If you're generating URLs dynamically on the server side, use your language's built-in URL encoding function (e.g., encodeURIComponent() in JavaScript, urlencode() in PHP) to ensure all special characters are properly escaped before inserting them into href attributes.
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
%5Band%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%5Band]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.
<ahref="mailto:[user@example.com]">Email Us</a>
Fixed: remove the brackets
<ahref="mailto:user@example.com">Email Us</a>
Invalid: square brackets in a query string
<ahref="https://example.com/search?filter[status]=active">Search</a>
Fixed: percent-encode the brackets
<ahref="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:
<ahref="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:
<ahref="mailto:user@example.com">Email Us</a>
In your template source, this might be written as:
<ahref="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.
A space character in the src attribute of an iframe is not valid in a URL. Spaces must be percent-encoded as %20 to produce a legal URL.
URLs follow the syntax rules defined in RFC 3986, which does not allow literal space characters anywhere in a URI, including the query string portion (the part after ?). When the HTML validator encounters a raw space in a URL attribute like src, it flags the value as malformed.
This commonly happens when query parameter values contain readable text, or when a URL is copied from a browser's address bar where the browser displays decoded spaces for readability. The fix is to replace each space with %20.
Some developers use + instead of %20 for spaces in query strings. The + convention originates from the application/x-www-form-urlencoded format used in HTML form submissions, and many servers accept it. However, %20 is the correct encoding according to RFC 3986 and will pass W3C validation without issues.
HTML examples
Invalid: space in the query string
<iframe
src="https://example.com/embed?title=my page&lang=en"
width="600"
height="400">
</iframe>
Valid: space encoded as %20
<iframe
src="https://example.com/embed?title=my%20page&lang=en"
width="600"
height="400">
</iframe>
Square brackets are not valid characters in a URL path segment and must be percent-encoded in the poster attribute of a <video> element.
The poster attribute accepts a valid URL that points to an image displayed while the video is not playing or until the first frame loads. URLs follow RFC 3986, which restricts the characters allowed in path segments. Square brackets ([ and ]) are reserved characters that are only permitted in the host component of a URL (for IPv6 addresses). When they appear in a path, query, or fragment component, they must be percent-encoded as %5B for [ and %5D for ].
This applies to any HTML attribute that expects a URL, not just poster. The same rule covers src, href, action, and similar attributes.
Invalid example
<videoposter="/images/thumbnail[1].jpg"controls>
<sourcesrc="video.mp4"type="video/mp4">
</video>
Fixed example
Replace [ with %5B and ] with %5D:
<videoposter="/images/thumbnail%5B1%5D.jpg"controls>
<sourcesrc="video.mp4"type="video/mp4">
</video>
If you control the file names on the server, renaming the file to avoid square brackets entirely is a simpler long term fix:
<videoposter="/images/thumbnail-1.jpg"controls>
<sourcesrc="video.mp4"type="video/mp4">
</video>
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
hrefvalues 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
<ahref="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
<ahref="https://example.com/search?query=hello%20world&lang=en">Search</a>
Correct — space encoded as + in query string
<ahref="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
<ahref="https://example.com/my folder/page?name=John Doe">Profile</a>
Correct — all spaces properly encoded
<ahref="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>
constquery="hello world";
consturl="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 containerrole="rowgroup"(optional) — groups rows together, like<thead>,<tbody>, or<tfoot>role="row"— a single row of cellsrole="cell"orrole="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.
<divrole="table">
<divrole="cell">Name</div>
<divrole="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.
<divrole="table">
<divrole="row">
<divclass="cell-wrapper">
<divrole="cell">Row 1, Cell 1</div>
<divrole="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.
<divrole="table"aria-label="Team members">
<divrole="row">
<divrole="columnheader">Name</div>
<divrole="columnheader">Email</div>
</div>
<divrole="row">
<divrole="cell">Alice</div>
<divrole="cell">alice@example.com</div>
</div>
<divrole="row">
<divrole="cell">Bob</div>
<divrole="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.
<divrole="table"aria-label="Quarterly results">
<divrole="rowgroup">
<divrole="row">
<divrole="columnheader">Quarter</div>
<divrole="columnheader">Revenue</div>
</div>
</div>
<divrole="rowgroup">
<divrole="row">
<divrole="cell">Q1</div>
<divrole="cell">$10,000</div>
</div>
<divrole="row">
<divrole="cell">Q2</div>
<divrole="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 like50%in a query string — the%must be written as%25. - Truncated percent-encoded sequences. A sequence like
%2is incomplete because it only has one hex digit instead of two. - Invalid hex characters after
%. A sequence like%GZis invalid becauseGandZare not hexadecimal digits (valid hex digits are0–9andA–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
hrefvalues 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 -->
<ahref="https://example.com?width=48%">48% width</a>
<!-- ✅ Good: the % is encoded as %25 -->
<ahref="https://example.com?width=48%25">48% width</a>
Truncated percent-encoded sequence
<!-- ❌ Bad: %2 is incomplete — it needs two hex digits -->
<ahref="https://example.com/path%2file">Download</a>
<!-- ✅ Good: %2F is a complete encoding for "/" -->
<ahref="https://example.com/path%2Ffile">Download</a>
Invalid hexadecimal characters
<!-- ❌ Bad: %XY contains non-hexadecimal characters -->
<ahref="https://example.com/search?q=%XYdata">Search</a>
<!-- ✅ Good: if the intent was a literal "%XY", encode the % -->
<ahref="https://example.com/search?q=%25XYdata">Search</a>
Multiple unencoded % in a URL
<!-- ❌ Bad: both % signs are unencoded -->
<ahref="https://example.com?min=10%&max=90%">Range</a>
<!-- ✅ Good: both % signs are properly encoded -->
<ahref="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
srcsetattribute is empty (srcset="") - The
srcsetattribute 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
srcattribute 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
<imgsrc="/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
<imgsrc="/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 ">
/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 "
/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
<imgsrc="/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 <tabs> element does not exist in the HTML specification and is not a valid HTML element.
Browsers parse unknown elements like <tabs> as generic inline elements with no semantics. Screen readers and other assistive technologies cannot interpret them correctly, so users who rely on those tools get no meaningful information about the content's purpose or structure.
If the goal is to create a tabbed interface, use standard HTML elements with appropriate ARIA roles. The WAI-ARIA Authoring Practices describe a well established tabs pattern built from <div>, <button>, and role attributes. Each tab is a <button> with role="tab", grouped inside a container with role="tablist". Each panel is a <div> with role="tabpanel".
HTML examples
Invalid: unknown <tabs> element
<tabs>
<tab>Tab 1</tab>
<tab>Tab 2</tab>
<tab-panel>Content for tab 1</tab-panel>
<tab-panel>Content for tab 2</tab-panel>
</tabs>
Valid: ARIA tabs pattern with standard elements
<divrole="tablist"aria-label="Sample tabs">
<buttonrole="tab"aria-selected="true"aria-controls="panel-1"id="tab-1">
Tab 1
</button>
<buttonrole="tab"aria-selected="false"aria-controls="panel-2"id="tab-2"tabindex="-1">
Tab 2
</button>
</div>
<divrole="tabpanel"id="panel-1"aria-labelledby="tab-1">
<p>Content for tab 1</p>
</div>
<divrole="tabpanel"id="panel-2"aria-labelledby="tab-2"hidden>
<p>Content for tab 2</p>
</div>
In this pattern, aria-selected indicates the active tab, aria-controls links each tab to its panel, and aria-labelledby links each panel back to its tab. The hidden attribute hides inactive panels. JavaScript is needed to toggle aria-selected, tabindex, and hidden when the user switches tabs.
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
dirattribute 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
dirto correctly resolve the ordering of these mixed runs of text. - Standards compliance: The WHATWG HTML Living Standard recommends that authors set the
dirattribute 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:
<htmldir="rtl"lang="ar">
For Hebrew:
<htmldir="rtl"lang="he">
For Persian:
<htmldir="rtl"lang="fa">
Examples
❌ Missing dir attribute on an Arabic document
<!DOCTYPE html>
<htmllang="ar">
<head>
<metacharset="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>
<htmldir="rtl"lang="ar">
<head>
<metacharset="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>قام المستخدم بزيارة <spandir="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 <o:p> element is a Microsoft Office namespace tag that has no meaning in HTML and is not part of any HTML specification.
When content is copied from Microsoft Word, Outlook, or other Office applications and pasted into an HTML document, the source often includes proprietary XML namespace elements like <o:p>, <o:OfficeDocumentSettings>, and similar tags. These belong to the urn:schemas-microsoft-com:office:office namespace and are only understood by Office applications. Browsers ignore them, but the W3C validator flags them as errors because they are not valid HTML elements.
The <o:p> tag specifically is used by Word to wrap paragraph content. It typically appears inside <p> elements and most often contains nothing or just a non-breaking space ( ). Removing it entirely has no effect on the rendered page.
If the <o:p> element wraps actual text content, replace it with a standard HTML element like <span> or simply keep the text without any wrapper. If it is empty or contains only , delete it.
HTML examples
Invalid: Office namespace element in HTML
<p>This is a paragraph.<o:p></o:p></p>
<p>
<o:p> </o:p>
</p>
Valid: Office elements removed
<p>This is a paragraph.</p>
When cleaning up Office-generated HTML, also look for other common namespace prefixes like <w:, <m:, and <v:, along with mso- prefixed CSS properties in style attributes. These are all Office artifacts and should be removed. Many text editors and CMS platforms have a "Paste as plain text" option that strips this markup automatically.
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
srcsetattribute 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
srcsetattribute. - Optionally add descriptors — use width descriptors (
w) when combined with thesizesattribute, or pixel density descriptors (x) for fixed-size images. - If you have no image to provide, remove the
<source>element entirely rather than leavingsrcsetempty. - Check dynamic output — if a CMS or templating engine generates the
srcsetvalue, add a conditional check to omit the<source>element when no images are available.
Examples
❌ Empty srcset attribute
<picture>
<sourcesrcset=""type="image/webp">
<imgsrc="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>
<sourcesrcset="photo.webp 400"type="image/webp">
<imgsrc="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>
<sourcesrcset="photo.webp"type="image/webp">
<imgsrc="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">
<imgsrc="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>
<sourcesrcset="photo.webp 1x, photo-2x.webp 2x"type="image/webp">
<imgsrc="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>
<imgsrc="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.
Spaces in the href attribute of an <a> element are not valid URL characters and must be encoded as %20.
URLs follow the syntax defined in RFC 3986, which does not allow literal space characters in any component of a URI. When a browser encounters a space in an href value, it often silently corrects it, but the HTML is still technically invalid. The W3C validator flags this because the raw space violates the URL specification.
To fix the issue, replace each space in the URL with %20. This is the percent-encoded form of the space character. If the URL points to a local file or path that genuinely contains spaces, the encoding is still required.
Some common situations where this appears:
- Links to files with spaces in their names, like
my document.pdf. - Paths that include folder names with spaces, like
/my folder/page.html. - Query parameters that contain unencoded spaces.
HTML examples
Invalid: space in href
<ahref="/my folder/my document.pdf">Download</a>
Valid: spaces encoded as %20
<ahref="/my%20folder/my%20document.pdf">Download</a>
If you control the file or directory names, renaming them to avoid spaces altogether is a simpler long term fix. Use hyphens or underscores instead:
<ahref="/my-folder/my-document.pdf">Download</a>
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.htmlbecomes/my%20file.html. - In the query string: You can use
%20or, if the value is part ofapplication/x-www-form-urlencodeddata,+is also acceptable for spaces within query parameter values. However,%20is universally safe. - Programmatically: Use
encodeURI()in JavaScript to encode a full URL (it preserves structural characters like/,?, and#). UseencodeURIComponent()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
<ahref="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
<ahref="https://example.com/docs/My%20Report.pdf">Download Report</a>
Invalid — space in a query parameter
<ahref="https://example.com/search?q=hello world">Search</a>
Fixed — space encoded in the query string
<ahref="https://example.com/search?q=hello%20world">Search</a>
Invalid — multiple spaces in different URL parts
<ahref="https://example.com/my folder/page two.html?ref=some value#my section">Link</a>
Fixed — all spaces encoded
<ahref="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.
consturl=encodeURI("https://example.com/docs/My Report.pdf");
// Result: "https://example.com/docs/My%20Report.pdf"
// encodeURIComponent encodes a single value (for query params)
constquery=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/2024instead of2024-03-15 - Using informal date formats:
March 15, 2024or15-03-2024 - Omitting the
Tseparator between date and time:2024-03-15 14:30 - Using 12-hour time with AM/PM:
2:30 PMinstead of14:30 - Providing incomplete values:
2024-3-5instead of2024-03-05(months and days must be zero-padded)
Examples
Invalid: Slash-separated date
<timedatetime="03/15/2024">March 15, 2024</time>
Valid: ISO 8601 date format
<timedatetime="2024-03-15">March 15, 2024</time>
Invalid: Missing T separator and using AM/PM
<timedatetime="2024-03-15 2:30 PM">March 15 at 2:30 PM</time>
Valid: Date-time with T separator and 24-hour time
<timedatetime="2024-03-15T14:30">March 15 at 2:30 PM</time>
Invalid: Informal time string
<timedatetime="half past two">2:30 PM</time>
Valid: Simple time value
<timedatetime="14:30">2:30 PM</time>
Invalid: Non-standard duration
<timedatetime="1 hour 30 minutes">1.5 hours</time>
Valid: ISO 8601 duration
<timedatetime="PT1H30M">1.5 hours</time>
Valid: Date-time with timezone offset
<p>The event starts at <timedatetime="2024-03-15T14:30-05:00">2:30 PM ET on March 15</time>.</p>
Valid: Using only the month
<p>Published in <timedatetime="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
targetviolates the HTML specification and produces a validation error. - Unpredictable browser behavior. While most browsers treat an empty
targetthe 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
targetsignals unclear intent. Removing it or using an explicit keyword makes the code easier to understand and maintain.
How to fix it
- Remove the
targetattribute if you want the link to open in the same browsing context. This is the default behavior, equivalent totarget="_self". - Use a valid keyword like
_self,_blank,_parent, or_topif 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
targetattribute 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:
<imgsrc="floor-plan.png"usemap="#rooms"alt="Floor plan">
<mapname="rooms">
<areashape="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:
<imgsrc="floor-plan.png"usemap="#rooms"alt="Floor plan">
<mapname="rooms">
<areashape="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:
<imgsrc="floor-plan.png"usemap="#rooms"alt="Floor plan">
<mapname="rooms">
<areashape="rect"coords="10,10,100,60"href="/kitchen"alt="Kitchen"target="_self">
<areashape="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:
<iframename="detailView"src="about:blank"title="Room details"></iframe>
<imgsrc="floor-plan.png"usemap="#rooms"alt="Floor plan">
<mapname="rooms">
<areashape="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:
<areashape="rect"coords="10,10,100,60"href="/kitchen"alt="Kitchen"
{%iftarget_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
maxto be a valid floating-point number when present. - Predictable validation: An empty
maxmeans 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
maxattribute 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"ormax="99.5"). - Remove the attribute: If no maximum is needed, remove the
maxattribute entirely rather than leaving it empty. - Fix dynamic templates: If your HTML is generated from a template, add a conditional check so that
maxis only rendered when a value is actually available.
Examples
❌ Invalid: Empty max attribute
<labelfor="quantity">Quantity:</label>
<inputtype="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
<labelfor="quantity">Quantity:</label>
<inputtype="number"id="quantity"name="quantity"max="100">
✅ Fixed: Removing the attribute entirely
<labelfor="quantity">Quantity:</label>
<inputtype="number"id="quantity"name="quantity">
If no maximum constraint is needed, simply omit the max attribute.
❌ Invalid: Empty max on a date input
<labelfor="end-date">End date:</label>
<inputtype="date"id="end-date"name="end-date"max="">
✅ Fixed: Valid date value for max
<labelfor="end-date">End date:</label>
<inputtype="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:
<inputtype="number"id="price"name="price"
{%ifmax_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.
Spaces in the src attribute of a <source> element are not valid URL characters and must be encoded or removed.
URLs follow strict syntax rules defined in RFC 3986. A space character is not permitted in any part of a URL path. When a file name or path contains spaces, you must replace each space with %20 (the percent-encoded form) or rename the file to avoid spaces altogether.
This applies to all elements that accept a URL, including <source>, <img>, <a>, <script>, and <link>. Browsers often handle spaces gracefully by encoding them automatically, but the HTML is still technically invalid and can cause issues in some contexts, such as when URLs are copied, shared, or processed by other tools.
The best practice is to avoid spaces in file names entirely. Use hyphens (-) or underscores (_) instead. If you can't rename the files, percent-encode the spaces.
Bad Example
<videocontrols>
<sourcesrc="videos/my cool video.mp4"type="video/mp4">
</video>
Good Example — Percent-Encoded Spaces
<videocontrols>
<sourcesrc="videos/my%20cool%20video.mp4"type="video/mp4">
</video>
Good Example — No Spaces in File Name
<videocontrols>
<sourcesrc="videos/my-cool-video.mp4"type="video/mp4">
</video>
Spaces in the src attribute of an img element are not valid URL characters and must be encoded as %20 or replaced with hyphens/underscores.
URLs follow the rules defined in RFC 3986, which does not allow literal space characters in any part of a URL. When a browser encounters a space in a src value, it may try to percent-encode it automatically, but this behavior is not guaranteed across all contexts. The W3C validator flags this because the HTML specification requires attribute values containing URLs to hold valid URL strings.
There are two ways to fix this: rename the file to remove spaces, or percent-encode the spaces as %20. Renaming is the better long term approach because it avoids encoding issues entirely and makes URLs easier to read and share.
Examples
Invalid: space in the file name
<imgsrc="my photo.jpg"alt="A photo">
Valid: percent-encoded space
<imgsrc="my%20photo.jpg"alt="A photo">
Valid: renamed file with no spaces
<imgsrc="my-photo.jpg"alt="A photo">
A mailto: link must not contain spaces between the colon and the email address.
The href attribute on an <a> element accepts mailto: URLs as defined in RFC 6068. The email address must immediately follow the colon with no whitespace. Browsers may still attempt to open the mail client, but the space makes the URL technically invalid and triggers a W3C validation error.
This applies to all parts of the mailto: URL. Spaces before the address, between multiple addresses, or before query parameters like ?subject= are all invalid. If you need to include a space in a subject or body parameter, encode it as %20.
HTML examples
Invalid
<ahref="mailto: user@example.com">Email us</a>
Valid
<ahref="mailto:user@example.com">Email us</a>
An <li> element inside a <ul>, <ol>, or <menu> must not be assigned any ARIA role other than listitem.
When an <li> sits inside a standard list container (<ul>, <ol>, or <menu>) that has no explicit role attribute, the browser already treats the <li> as having an implicit role of listitem. Assigning a different role, such as role="presentation", role="option", or role="tab", contradicts the semantics of the parent-child relationship between the list and its items. Screen readers and other assistive technologies rely on this relationship to convey list structure to users.
The same restriction applies when the parent element has role="list" set explicitly. In that context, child <li> elements are still expected to carry only the listitem role.
If you need a different role on the items, restructure the markup. For example, use <div> elements instead of <ul> and <li>, and apply the appropriate roles directly. Or, if the items genuinely are list items, remove the conflicting role attribute.
Examples
Invalid: li with a non-listitem role inside a ul
<ul>
<lirole="tab">Dashboard</li>
<lirole="tab">Settings</li>
<lirole="tab">Profile</li>
</ul>
Fixed: use appropriate elements when a different role is needed
If the items are tabs, drop the list markup and use elements that match the intended semantics:
<divrole="tablist">
<buttonrole="tab"aria-selected="true">Dashboard</button>
<buttonrole="tab"aria-selected="false">Settings</button>
<buttonrole="tab"aria-selected="false">Profile</button>
</div>
Fixed: keep the list but remove the conflicting role
If the content really is a list, remove the extra role:
<ul>
<li>Dashboard</li>
<li>Settings</li>
<li>Profile</li>
</ul>
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