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.
In CSS, most numeric values represent a length, time, angle, or other dimension, and the unit tells the browser how to interpret the number. Writing padding: 20 is ambiguous — does it mean 20 pixels, 20 ems, or 20 percent? Without a unit, the browser has no way to determine the author's intent, so it treats the value as invalid and discards the entire declaration. This can lead to broken layouts, missing spacing, or other visual problems that are difficult to debug.
The only exception to this rule is 0. Zero pixels is the same as zero ems, zero rems, or zero percent — it's always nothing regardless of the unit. Because of this, the CSS specification allows 0 to be written without a unit. In fact, omitting the unit on zero values is considered a best practice for cleaner, more concise CSS.
This validation error commonly appears in inline style attributes, which is where the W3C HTML Validator encounters it. However, the same rule applies to CSS written in <style> elements and external stylesheets.
Common causes of this error include:
- Accidentally forgetting to type the unit after a number.
- Copy-pasting values from design tools that don't include units.
- Confusing CSS with JavaScript's
element.style.width = "50"pattern (where some APIs accept unitless numbers). - Assuming that
pxis the default unit (it is not — there is no default unit in CSS).
Examples
Incorrect — missing units on numeric values
<divstyle="margin:10;padding:20;width:300;">
Content here
</div>
The validator will report errors for 10, 20, and 300 because none of them have units.
Correct — units specified on all non-zero values
<divstyle="margin:10px;padding:20px;width:300px;">
Content here
</div>
Correct — zero without a unit
<divstyle="margin:0;padding:0;border:0;">
No units needed for zero
</div>
Correct — mixing zero and non-zero values
<divstyle="margin:010px020px;">
Top and bottom margins are zero, sides have units
</div>
Correct — using other unit types
<pstyle="font-size:1.2em;line-height:1.5em;margin-bottom:2rem;">
Text with relative units
</p>
Note that line-height is a special case in CSS — it actually accepts a unitless number (like 1.5) as a valid value, where the number acts as a multiplier of the element's font size. This is not the same as a missing unit; it's a deliberately unitless ratio defined in the specification. Most other properties do not have this behavior.
The sizes attribute and the srcset attribute are designed to work as a pair for responsive image delivery. The srcset attribute provides the browser with a list of image files and their intrinsic widths (e.g., 480w, 800w), while the sizes attribute tells the browser how much space the image will occupy in the layout at different viewport sizes. The browser combines this information to select the most appropriate image file to download.
When sizes appears without srcset, it serves no purpose. The browser has only the single image specified in the src attribute, so there's no decision to make about which image to load. The HTML specification explicitly requires that sizes must not be present unless srcset is also specified with width descriptors.
This error commonly occurs when a CMS or templating system outputs the sizes attribute by default, when srcset is accidentally removed during refactoring, or when developers copy markup snippets without including all the necessary attributes.
Beyond standards compliance, leaving orphaned sizes attributes creates confusing, harder-to-maintain code. Other developers (or your future self) may assume responsive images are configured when they aren't, leading to wasted debugging time.
How to fix it
You have two options:
- Add a
srcsetattribute if you want the browser to choose from multiple image sizes based on viewport width. Thesrcsetmust use width descriptors (w) forsizesto be meaningful. - Remove the
sizesattribute if you don't need responsive images and a singlesrcis sufficient.
Note that sizes is also valid on <source> elements inside a <picture> element — the same rule applies there. Every <source> with a sizes attribute must also have a srcset attribute.
Examples
❌ Incorrect: sizes without srcset
<img
src="image.jpg"
sizes="(max-width: 600px) 480px, 800px"
alt="A mountain landscape">
The sizes attribute is present but there's no srcset, so the browser has no alternative images to pick from.
✅ Correct: sizes paired with srcset
<img
src="image-800w.jpg"
srcset="image-480w.jpg 480w, image-800w.jpg 800w"
sizes="(max-width: 600px) 480px, 800px"
alt="A mountain landscape">
Here, srcset provides two images with their intrinsic widths. The sizes attribute tells the browser: "If the viewport is 600px or narrower, the image will display at 480px wide; otherwise, it will display at 800px wide." The browser uses this information to download the most efficient file.
✅ Correct: removing sizes when responsive images aren't needed
<imgsrc="image.jpg"alt="A mountain landscape">
If a single image is sufficient, simply drop the sizes attribute.
❌ Incorrect: sizes on a <source> without srcset
<picture>
<source
media="(min-width: 800px)"
sizes="50vw">
<imgsrc="fallback.jpg"alt="A sunset over the ocean">
</picture>
✅ Correct: sizes on a <source> with srcset
<picture>
<source
media="(min-width: 800px)"
srcset="wide-480w.jpg 480w, wide-960w.jpg 960w"
sizes="50vw">
<imgsrc="fallback.jpg"alt="A sunset over the ocean">
</picture>
The <source> element now includes a srcset with width descriptors, giving the browser the candidate images it needs to make use of sizes.
A percentage value in your CSS exceeds the allowed range of 0 to 100.
CSS properties that accept percentage values — such as opacity, width, height, and others used in certain contexts — may be restricted to specific ranges. When you embed CSS in an HTML document (via the style attribute or a <style> element), the W3C HTML Validator checks these values and flags any that fall outside the permitted range.
A value like 100.01% is just slightly over the maximum of 100%. This is often a typo or a rounding error. While most browsers will silently clamp the value to 100%, it is still invalid and should be corrected.
HTML Examples
❌ Invalid: value out of range
<divstyle="width:100.01%;">Content</div>
✅ Fixed: value within range
<divstyle="width:100%;">Content</div>
HTML follows strict nesting rules: elements must be closed in the reverse order they were opened, like a stack. When the validator encounters </X> but the current open element is Y, it means something has gone wrong in the document structure. The browser's parser will attempt to recover from this mismatch, but the result may not reflect your intended layout or semantics.
There are several common causes for this error:
- Typos in tag names — for example, opening a
<div>but closing it with</dvi>. - Mismatched tags — opening one element but closing a different one, such as
<strong>closed with</em>. - Incorrect nesting order — overlapping elements where tags cross boundaries instead of being properly nested.
- Missing closing tags — a forgotten closing tag causes subsequent closing tags to be misaligned with the parser's expectation.
This matters because improperly nested HTML can cause unpredictable rendering across browsers, break CSS styling, interfere with JavaScript DOM manipulation, and create accessibility problems for screen readers that rely on a well-formed document tree.
To fix this error, trace back from the reported line to find where the mismatch originates. Ensure that every opening tag has a corresponding closing tag with the exact same name, and that elements are closed in the correct order (last opened, first closed).
Examples
Typo in the closing tag
<!-- ❌ Wrong: closing tag is misspelled -->
<section>
<p>Hello world</p>
</secton>
<!-- ✅ Fixed: closing tag matches the opening tag -->
<section>
<p>Hello world</p>
</section>
Mismatched tags
<!-- ❌ Wrong: <strong> is closed with </em> -->
<p>This is <strong>important</em> text.</p>
<!-- ✅ Fixed: closing tag matches the opening tag -->
<p>This is <strong>important</strong> text.</p>
Incorrectly nested (overlapping) elements
<!-- ❌ Wrong: <b> and <i> overlap each other -->
<p><b>Bold <i>and italic</b> text</i></p>
The validator sees </b> when the current open element is <i>. Elements must nest completely inside one another without overlapping.
<!-- ✅ Fixed: elements are properly nested -->
<p><b>Bold <i>and italic</i></b><i> text</i></p>
Missing closing tag causing a cascade of errors
<!-- ❌ Wrong: missing </div> for the inner div -->
<divclass="outer">
<divclass="inner">
<p>Content</p>
</div>
Here the single </div> closes inner, leaving outer unclosed. If more HTML follows, subsequent closing tags will be misaligned, potentially producing this error further down in the document.
<!-- ✅ Fixed: both divs are properly closed -->
<divclass="outer">
<divclass="inner">
<p>Content</p>
</div>
</div>
Tips for debugging
- Work from the first error — in HTML validation, one early mistake can cascade into many subsequent errors. Fix the first reported mismatch and re-validate before tackling later errors.
- Use consistent indentation — properly indented code makes it much easier to spot where nesting goes wrong.
- Use an editor with bracket/tag matching — most code editors can highlight matching opening and closing tags, making mismatches immediately visible.
In earlier versions of HTML (HTML 4 and XHTML), the type attribute was required on the <style> element to declare the MIME type of the styling language being used. The value was almost always text/css, as CSS has been the dominant stylesheet language for the web since its inception.
With HTML5, the specification changed. The type attribute on <style> now defaults to text/css, and since no browser supports any other styling language, the attribute serves no practical purpose. The WHATWG HTML Living Standard explicitly notes that the attribute is unnecessary and can be omitted. The W3C validator flags its presence as a warning to encourage cleaner, more modern markup.
Why This Matters
- Cleaner code: Removing unnecessary attributes reduces file size (even if marginally) and improves readability. Every attribute should earn its place in your markup.
- Standards compliance: Modern HTML encourages omitting default values when they add no information. Including
type="text/css"signals outdated coding practices. - Consistency: The same principle applies to
<script>elements, wheretype="text/javascript"is also unnecessary. Keeping your markup consistent by omitting both makes your codebase easier to maintain.
How to Fix It
The fix is straightforward: find every <style> element in your HTML that includes a type attribute and remove it. No other changes are needed — the browser behavior will be identical.
If you're working on a large codebase, a simple search for <style type= across your files will help you find all instances.
Examples
❌ Incorrect: Redundant type attribute
<styletype="text/css">
p{
color: red;
}
</style>
<p>This text will be red.</p>
The type="text/css" attribute is unnecessary and triggers the W3C validator warning.
✅ Correct: type attribute omitted
<style>
p{
color: red;
}
</style>
<p>This text will be red.</p>
Without the type attribute, the browser still interprets the contents as CSS — the behavior is exactly the same.
❌ Incorrect: Other variations that also trigger the warning
The warning is triggered regardless of how the type value is formatted:
<styletype="text/css"media="screen">
body{
font-family: sans-serif;
}
</style>
✅ Correct: Other attributes are fine, just remove type
<stylemedia="screen">
body{
font-family: sans-serif;
}
</style>
Note that other valid attributes like media or nonce should be kept — only the type attribute needs to be removed.
The <script> element serves two distinct purposes in HTML: loading executable scripts and embedding non-executable data blocks. When the src attribute is present, the element is always being used to load an external script, so the type attribute must reflect a valid script type. Setting type to something like "text/html", "text/plain", or an invented value like "wrong" tells the browser this is not JavaScript, which means the external file referenced by src will be fetched but never executed — almost certainly not what the author intended.
The HTML specification restricts the allowed type values for <script src="..."> to three categories:
- An empty string (
type=""): Treated the same as the default, which is JavaScript. - A JavaScript MIME type: This includes
text/javascript,application/javascript, and other legacy JavaScript MIME types. Sincetext/javascriptis the default, specifying it is redundant. module: Indicates the script should be treated as a JavaScript module, enablingimport/exportsyntax and deferred execution by default.
Any value outside these categories — such as text/html, application/json, or a made-up string — is invalid when src is present.
Why this matters
Broken functionality: A non-JavaScript type on a <script> with src prevents the browser from executing the loaded file. The script is effectively dead code that still costs a network request.
Standards compliance: The HTML living standard explicitly forbids this combination. Validators flag it because it almost always indicates a mistake — either the wrong type was applied, or the src attribute was added by accident.
Maintainability: Future developers reading the code may be confused about whether the script is supposed to execute or serve as an inert data block. Keeping markup valid makes intent clear.
How to fix it
- Remove the
typeattribute entirely. This is the best approach for classic JavaScript. The default behavior istext/javascript, so notypeis needed. - Use
type="module"if the script uses ES module syntax (import/export). - If you intended a data block (e.g., embedding JSON or a template), remove the
srcattribute and place the content inline inside the<script>element instead. Data blocks with non-JavaScript types cannot usesrc.
Examples
Invalid: non-JavaScript types with src
These all trigger the validation error because the type value is not a JavaScript MIME type, an empty string, or "module":
<scripttype="text/html"src="template.html"></script>
<scripttype="application/json"src="data.json"></script>
<scripttype="text/plain"src="app.js"></script>
<scripttype="wrong"src="app.js"></script>
Valid: omitting the type attribute
The simplest and recommended fix for classic scripts — just drop type:
<scriptsrc="app.js"></script>
Valid: using a JavaScript MIME type
This is valid but redundant, since text/javascript is already the default. The validator may suggest omitting it:
<scripttype="text/javascript"src="app.js"></script>
Valid: using type="module"
Use this when the external script uses ES module syntax:
<scripttype="module"src="app.js"></script>
Valid: using an empty type attribute
An empty string is treated as the default. It's valid but unnecessary, and the validator may suggest removing it:
<scripttype=""src="app.js"></script>
Valid: data blocks without src
If you need a non-JavaScript type for an inline data block, remove the src attribute and place the content directly inside the element:
<scripttype="application/json"id="config">
{
"apiUrl":"https://example.com/api",
"debug":false
}
</script>
The X-UA-Compatible directive was introduced to control which rendering engine Internet Explorer would use when displaying a page. Setting it to a specific version like IE=10 locks the page into that version's rendering mode, even if the user has a newer version of Internet Explorer installed. This is problematic because it prevents the browser from using its most capable, standards-compliant rendering engine and can lead to unexpected layout or functionality issues.
The value IE=edge tells Internet Explorer to use the latest rendering engine available, which ensures the best possible standards compliance and feature support. The HTML specification and the W3C validator enforce this requirement because pinning to a specific IE version serves no forward-compatible purpose and can actively degrade the browsing experience.
It's worth noting that with Internet Explorer now being retired and replaced by Microsoft Edge, this meta tag is largely historical. However, if you include it at all, the validator requires that it be set to IE=edge. If your site no longer needs to support legacy versions of Internet Explorer, you can also simply remove the X-UA-Compatible declaration entirely — modern browsers ignore it.
How to fix it
- Find any
<meta http-equiv="X-UA-Compatible">tag in your HTML<head>. - Change the
contentattribute value from the specific version (e.g.,IE=10) toIE=edge. - If the directive is set as an HTTP response header on your server, update the header value there as well.
- Alternatively, remove the tag or header entirely if you no longer need IE compatibility.
Examples
Incorrect: pinned to a specific IE version
This triggers the validator error because IE=10 locks rendering to Internet Explorer 10 mode:
<metahttp-equiv="X-UA-Compatible"content="IE=10">
Other version-specific values that would also trigger this error include IE=9, IE=11, IE=EmulateIE10, and similar variations.
Correct: using IE=edge
Replace the version-specific value with IE=edge:
<metahttp-equiv="X-UA-Compatible"content="IE=edge">
Correct: full document example
When included in a complete HTML document, the X-UA-Compatible meta tag should appear early in the <head>, ideally right after the <meta charset> declaration:
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<metahttp-equiv="X-UA-Compatible"content="IE=edge">
<title>My Page</title>
</head>
<body>
<p>Page content goes here.</p>
</body>
</html>
Correct: removing the tag entirely
If IE support is no longer a concern, the simplest fix is to remove the meta tag altogether:
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>My Page</title>
</head>
<body>
<p>Page content goes here.</p>
</body>
</html>
Server-side HTTP header
If the X-UA-Compatible value is being sent as an HTTP response header rather than a meta tag, update your server configuration. For example, in Apache:
# Incorrect
Header set X-UA-Compatible "IE=10"
# Correct
Header set X-UA-Compatible "IE=edge"
Or in Nginx:
# Incorrect
add_header X-UA-Compatible "IE=10";
# Correct
add_header X-UA-Compatible "IE=edge";
The @import rule allows you to pull in styles from another CSS file. According to the CSS specification, @import rules must precede all other at-rules (except @charset) and all style rules. When a browser encounters an @import after another valid CSS statement, it silently ignores the import. This means the styles you intended to load simply won't be applied, which can lead to broken layouts or missing designs that are difficult to debug.
The W3C validator catches this ordering problem and flags it because the misplaced @import will have no effect. Even though browsers won't throw a visible error, the imported stylesheet is completely discarded, making this a real functional issue rather than just a cosmetic validation warning.
The correct order inside a <style> element or CSS file is:
@charset(if needed, and only in external CSS files)@importrules- All other CSS rules (
@font-face,@media, style rules, etc.)
Examples
Incorrect: @import after a style rule
<style>
body{
margin:0;
}
@importurl("typography.css");
h1{
color: navy;
}
</style>
The @import is placed after the body rule, so the browser will ignore it entirely. The styles from typography.css will never be loaded.
Incorrect: @import after another at-rule
<style>
@font-face{
font-family:"MyFont";
src:url("myfont.woff2")format("woff2");
}
@importurl("reset.css");
</style>
Even though @font-face is an at-rule, it is not @charset or @import, so placing @import after it is invalid.
Correct: @import rules first
<style>
@importurl("reset.css");
@importurl("typography.css");
@font-face{
font-family:"MyFont";
src:url("myfont.woff2")format("woff2");
}
body{
margin:0;
}
h1{
color: navy;
}
</style>
All @import rules appear before any other statements, so they are valid and the imported stylesheets will be loaded correctly.
Correct: Multiple @import rules with @charset
In an external CSS file, you may also have a @charset declaration. It must come first, followed by @import rules:
@charset"UTF-8";
@importurl("base.css");
@importurl("components.css");
body{
font-family: sans-serif;
}
Alternatives to @import
While fixing the ordering resolves the validation error, it's worth noting that @import in CSS can cause performance issues because imported files are loaded sequentially rather than in parallel. Consider these alternatives:
- Multiple
<link>elements in your HTML<head>— these allow browsers to download stylesheets in parallel. - CSS bundling tools — build tools like Webpack, Vite, or PostCSS can combine multiple CSS files into one at build time, eliminating the need for
@importat runtime.
If you do use @import, just make sure every instance appears at the very top of your stylesheet, before any other CSS rules.
A | (pipe) character in the fragment portion of a URL must be percent-encoded as %7C to be valid.
The href attribute on an <a> element must contain a valid URL. According to the URL specification, certain characters — including the pipe | — are not permitted as literal characters in a URL fragment (the part after #). The W3C validator flags this because the browser may handle it inconsistently, and it violates the URL standard.
To fix it, replace every | in the URL with its percent-encoded equivalent: %7C.
HTML Examples
❌ Invalid: literal pipe in fragment
<ahref="https://example.com/page#section|one">Link</a>
✅ Valid: percent-encoded pipe in fragment
<ahref="https://example.com/page#section%7Cone">Link</a>
If you control the target page, consider redesigning the fragment identifiers to avoid special characters altogether — for example, using hyphens or underscores like #section-one instead.
The W3C HTML Validator checks that URLs used in attributes like href conform to the URL Standard maintained by WHATWG. According to this standard, only certain characters are permitted to appear literally in the query component of a URL. The pipe character (|, Unicode U+007C) is not in the set of allowed query characters, which means it must be percent-encoded as %7C when it appears in a URL's query string.
While most modern browsers will silently handle a raw | in a URL and still navigate to the intended destination, relying on this behavior is problematic for several reasons:
- Standards compliance: HTML documents that contain unencoded special characters in URLs are technically invalid and will fail W3C validation.
- Interoperability: Not all user agents, HTTP clients, web scrapers, or proxy servers handle illegal URL characters the same way. An unencoded pipe could be misinterpreted, stripped, or cause unexpected behavior in certain environments.
- Security: Properly encoding URLs helps prevent injection attacks and ensures that each part of the URL is unambiguously parsed. Unencoded special characters can be exploited in certain contexts.
- Link sharing and processing: URLs are often copied, pasted, embedded in emails, or processed by APIs. An unencoded
|may break the URL when it passes through systems that strictly enforce URL syntax.
This issue commonly arises when URLs are constructed by hand, pulled from databases, or generated by backend systems that don't automatically encode query parameters. It can also appear when using pipe-delimited values as query parameter values (e.g., ?filter=red|blue|green).
The fix is straightforward: replace every literal | in the URL with its percent-encoded equivalent %7C. If you're generating URLs in code, use built-in encoding functions like JavaScript's encodeURIComponent() or PHP's urlencode() to handle this automatically.
Examples
Incorrect: raw pipe character in query string
<ahref="https://example.com/search?q=test|demo">Search</a>
The literal | in the query string triggers the validation error.
Correct: pipe character percent-encoded
<ahref="https://example.com/search?q=test%7Cdemo">Search</a>
Replacing | with %7C makes the URL valid. The server receiving this request will decode it back to test|demo automatically.
Incorrect: multiple pipe characters as delimiters
<ahref="https://example.com/filter?colors=red|blue|green">Filter colors</a>
Correct: all pipe characters encoded
<ahref="https://example.com/filter?colors=red%7Cblue%7Cgreen">Filter colors</a>
Generating encoded URLs in JavaScript
If you're building URLs dynamically, use encodeURIComponent() to encode individual parameter values:
<script>
constcolors="red|blue|green";
consturl="https://example.com/filter?colors="+encodeURIComponent(colors);
// Result: "https://example.com/filter?colors=red%7Cblue%7Cgreen"
</script>
This ensures that any special characters in the value — including |, spaces, ampersands, and others — are properly encoded without you needing to remember each character's percent-encoded form.
Other characters to watch for
The pipe character is not the only one that causes this validation error. Other characters that must be percent-encoded in URL query strings include curly braces ({ and }), the caret (^), backtick (`), and square brackets ([ and ]) when used outside of specific contexts. As a general rule, always encode user-supplied or dynamic values using your language's URL encoding function rather than constructing query strings through simple string concatenation.
Microdata is an HTML specification that lets you embed machine-readable data into your content using three main attributes: itemscope, itemtype, and itemprop. The itemscope attribute creates a new item (a group of name-value pairs), itemtype specifies what kind of thing the item is (using a vocabulary URL like Schema.org), and itemprop defines individual properties within that item. These attributes work together — itemprop only makes sense in the context of an itemscope.
When the validator encounters an itemprop attribute on an element that isn't a descendant of any element with itemscope, it has no way to associate that property with an item. The property is essentially orphaned. This is a problem for several reasons:
- Search engines can't use the data. Structured data consumers like Google, Bing, and other crawlers rely on the
itemscope/itemprophierarchy to understand your content. An orphaneditempropis ignored or misinterpreted. - Standards compliance. The WHATWG HTML living standard requires that an element with
itempropmust be a property of an item — meaning it must have an ancestor withitemscope, or be explicitly referenced via theitemrefattribute on anitemscopeelement. - Maintenance issues. Orphaned
itempropattributes suggest that surrounding markup was refactored and the microdata structure was accidentally broken.
The most common causes of this error are:
- Missing
itemscope— You addeditempropattributes but forgot to define the containing item withitemscope. - Moved elements — An element with
itempropwas moved outside of its originalitemscopecontainer during a refactor. - Copy-paste errors — You copied a snippet that included
itempropbut not the parentitemscope.
To fix the issue, either wrap the itemprop elements inside an itemscope container, use itemref to associate distant properties with an item, or remove the itemprop attribute if structured data is not intended.
Examples
Incorrect: itemprop without itemscope
This triggers the validation error because there is no itemscope ancestor:
<div>
<p>My name is <spanitemprop="name">Liza</span>.</p>
</div>
Correct: itemprop inside an itemscope container
Adding itemscope (and optionally itemtype) to an ancestor element fixes the issue:
<divitemscopeitemtype="https://schema.org/Person">
<p>My name is <spanitemprop="name">Liza</span>.</p>
</div>
Correct: nested items with their own scope
When an item contains a sub-item, the nested item needs its own itemscope:
<divitemscopeitemtype="https://schema.org/Person">
<pitemprop="name">Liza</p>
<divitemprop="address"itemscopeitemtype="https://schema.org/PostalAddress">
<spanitemprop="addressLocality">Portland</span>,
<spanitemprop="addressRegion">OR</span>
</div>
</div>
Correct: using itemref for properties outside the scope
If you can't restructure your HTML to nest itemprop inside itemscope, use itemref to reference elements by their id:
<divitemscopeitemtype="https://schema.org/Person"itemref="user-name"></div>
<pid="user-name">
My name is <spanitemprop="name">Liza</span>.
</p>
In this case, the itemprop="name" element is not a descendant of the itemscope element, but the itemref="user-name" attribute explicitly pulls the referenced element's tree into the item, making it valid.
Incorrect: scope broken after refactoring
A common real-world scenario where the error appears after restructuring:
<divitemscopeitemtype="https://schema.org/Product">
<spanitemprop="name">Widget</span>
</div>
<!-- This was moved out of the div above -->
<spanitemprop="price">9.99</span>
Fix this by either moving the element back inside the itemscope container, using itemref, or removing the orphaned itemprop.
In a URL, the # character has a special role: it acts as the delimiter that separates the main URL from the fragment identifier. The fragment typically points to a specific section or element within the target document, often corresponding to an element's id attribute. Because # serves this reserved purpose, it cannot appear more than once in its raw form within a URL. When the validator encounters something like ##pricing or section#one#two, it flags the extra # characters as illegal.
This issue usually arises from one of these common scenarios:
- Typos — accidentally typing
##instead of#. - String concatenation bugs — building URLs programmatically where a
#is included both in the base URL and prepended to the fragment value. - Copy-paste errors — duplicating the
#when copying URLs from browser address bars or other sources. - Literal
#intended in fragment — if you genuinely need a#symbol within the fragment text, it must be percent-encoded as%23.
This matters because browsers may handle malformed URLs inconsistently. Some browsers silently strip the extra #, while others may fail to navigate to the intended fragment. Malformed URLs also cause problems for assistive technologies, web crawlers, and any tooling that parses links. Keeping your URLs well-formed ensures predictable behavior across all user agents and complies with the URL Standard and HTML specification.
Examples
Incorrect: duplicate # in the URL
The double ## makes the fragment identifier invalid:
<ahref="https://example.com/faqs##pricing">Pricing</a>
Correct: single # delimiter
Remove the extra # so that pricing is the fragment:
<ahref="https://example.com/faqs#pricing">Pricing</a>
Incorrect: extra # inside the fragment
Here, the fragment portion overview#details contains a raw #, which is not allowed:
<ahref="/docs#overview#details">Details</a>
Correct: percent-encode the literal #
If you truly need a # as part of the fragment text, encode it as %23:
<ahref="/docs#overview%23details">Details</a>
In most cases though, this pattern suggests the URL structure should be rethought. A cleaner approach is to link directly to the intended fragment:
<ahref="/docs#details">Details</a>
Incorrect: programmatic concatenation error
A common bug in templates or JavaScript is prepending # when the variable already includes it:
<!-- If defined as defined as fragment = "#pricing", this produces a double ## -->
<ahref="https://example.com/faqs#pricing">Pricing</a>
Correct: ensure only one # is present
Make sure either the base URL or the fragment variable includes the #, but not both:
<ahref="https://example.com/faqs#pricing">Pricing</a>
Fragment-only links
Fragment-only links (links to sections within the same page) follow the same rule — only one #:
<!-- Incorrect -->
<ahref="##contact">Contact Us</a>
<!-- Correct -->
<ahref="#contact">Contact Us</a>
The URL specification (defined by WHATWG and RFC 3986) restricts which characters can appear unencoded in different parts of a URL. Square brackets are reserved characters that have a specific meaning in URLs — they are only permitted in the host portion (to wrap IPv6 addresses like [::1]). In the query string, they must be percent-encoded.
This issue commonly appears when frameworks (especially PHP, Ruby on Rails, or JavaScript libraries) generate URLs with array-style query parameters like ?filter[color]=red. While many browsers and servers are lenient and will interpret these URLs correctly, they are technically invalid according to the URL standard. The W3C validator enforces this rule strictly.
Beyond standards compliance, using unencoded square brackets can cause problems in practice. Some HTTP clients, proxies, or caching layers may reject or mangle URLs containing raw brackets. Percent-encoding these characters ensures your URLs are universally safe and interoperable across all systems.
How to fix it
You have two main approaches:
Percent-encode the brackets. Replace every
[with%5Band every]with%5Din the URL. This preserves the parameter structure that your server or framework expects, while making the URL valid.Restructure the query parameters. If your backend allows it, use flat parameter names with dot notation, underscores, or dashes instead of bracket syntax. For example, change
size[width]tosize_widthorsize.width.
If you're generating URLs in JavaScript, the built-in encodeURI() function does not encode square brackets. Use encodeURIComponent() on individual parameter names or values, or manually replace [ and ] after constructing the URL.
Examples
❌ Invalid: unencoded square brackets in query string
<imgsrc="/images/photo.jpg?size[width]=300&size[height]=200"alt="A sample photo">
The literal [ and ] characters in the query string trigger the validation error.
✅ Fixed: percent-encoded brackets
<imgsrc="/images/photo.jpg?size%5Bwidth%5D=300&size%5Bheight%5D=200"alt="A sample photo">
Replacing [ with %5B and ] with %5D makes the URL valid. Most servers and frameworks will decode these automatically and interpret the parameters the same way.
✅ Fixed: flat parameter names without brackets
<imgsrc="/images/photo.jpg?size_width=300&size_height=200"alt="A sample photo">
If you control the server-side logic, you can avoid brackets altogether by using a flat naming convention for your parameters.
❌ Invalid: brackets in more complex query strings
<img
src="/api/image?filters[type]=jpeg&filters[quality]=80&crop[x]=10&crop[y]=20"
alt="Processed image">
✅ Fixed: all brackets encoded
<img
src="/api/image?filters%5Btype%5D=jpeg&filters%5Bquality%5D=80&crop%5Bx%5D=10&crop%5By%5D=20"
alt="Processed image">
Encoding in JavaScript
If you build image URLs dynamically, handle the encoding in your code:
// Manual replacement approach
consturl="/images/photo.jpg?size[width]=300&size[height]=200";
constsafeUrl=url.replace(/\[/g,"%5B").replace(/\]/g,"%5D");
img.src=safeUrl;
// Using URLSearchParams (automatically encodes brackets)
constparams=newURLSearchParams();
params.set("size[width]","300");
params.set("size[height]","200");
img.src=`/images/photo.jpg?${params.toString()}`;
Both approaches produce a valid, properly encoded URL that will pass W3C validation.
Why This Matters
While HTML5 is quite permissive with id values (allowing almost anything except spaces), elements within XML-based vocabularies like SVG and MathML are held to stricter rules. When these elements appear in your HTML document, their attributes must still conform to XML 1.0 naming conventions as defined by the relevant specification.
XML 1.0 names must follow these rules:
- Must start with a letter (
a–z,A–Z) or an underscore (_) - Subsequent characters can be letters, digits (
0–9), hyphens (-), underscores (_), and periods (.) - Cannot contain spaces, colons (outside of namespaced contexts), or special characters like
@,#,$,!, etc.
This error typically appears when design tools (such as Figma, Illustrator, or Sketch) export SVG files with auto-generated id values that include spaces or other invalid characters. Browsers may still render the content, but relying on non-conformant names can cause problems with CSS selectors, JavaScript's getElementById(), URL fragment references, and accessibility tools that depend on valid identifiers.
How to Fix It
- Remove spaces — replace them with hyphens or underscores, or use camelCase.
- Ensure the name starts with a letter or underscore — if it starts with a digit, prefix it with a letter or underscore.
- Strip out special characters — remove or replace characters like
@,#,(,), etc. - Review exported SVG files — if you're embedding SVGs from design tools, clean up the generated
idvalues before adding them to your HTML.
Examples
Invalid: Space in the id value
The space in "Group 270" makes this an invalid XML 1.0 name:
<svgviewBox="0 0 100 100"xmlns="http://www.w3.org/2000/svg">
<gid="Group 270">
<circlecx="50"cy="50"r="40"/>
</g>
</svg>
Invalid: Name starts with a digit
XML 1.0 names cannot begin with a number:
<svgviewBox="0 0 100 100"xmlns="http://www.w3.org/2000/svg">
<rectid="1st-rectangle"width="100"height="50"/>
</svg>
Invalid: Special characters in the name
Characters like ( and ) are not allowed:
<svgviewBox="0 0 200 100"xmlns="http://www.w3.org/2000/svg">
<pathid="icon(home)"d="M10 80 L50 10 L90 80 Z"/>
</svg>
Fixed: Valid XML 1.0 names
Replace spaces with hyphens, prefix digit-leading names with a letter, and remove special characters:
<svgviewBox="0 0 200 100"xmlns="http://www.w3.org/2000/svg">
<gid="group-270">
<circlecx="50"cy="50"r="40"/>
</g>
<rectid="first-rectangle"width="100"height="50"/>
<pathid="icon-home"d="M10 80 L50 10 L90 80 Z"/>
</svg>
Tip: Cleaning up exported SVGs
Design tools often produce id values like "Frame 42", "Vector (Stroke)", or "123_layer". A quick find-and-replace workflow can fix these before they land in your codebase. You can also use tools like SVGO to optimize and clean up SVG output, including stripping or renaming invalid identifiers.
The <pattern> element lives inside <svg>, and SVG is an XML-based language. Unlike regular HTML — where id values follow relatively relaxed rules — SVG content must comply with XML 1.0 naming conventions. This means id values have stricter character and formatting requirements than you might be used to in plain HTML.
XML 1.0 Name Rules
An XML 1.0 name (used for id attributes in SVG) must follow these rules:
- First character must be a letter (
A–Z,a–z) or an underscore (_). - Subsequent characters can be letters, digits (
0–9), hyphens (-), underscores (_), or periods (.). - Spaces and special characters like
!,@,#,$,%,(,), etc. are not allowed anywhere in the name.
Common mistakes that trigger this error include starting an id with a digit (e.g., 1pattern), a hyphen (e.g., -myPattern), or a period (e.g., .dotPattern), or including characters like spaces or colons.
Why This Matters
- Standards compliance: SVG is parsed as XML in many contexts. An invalid XML name can cause parsing errors or unexpected behavior, especially when SVG is served with an XML MIME type or embedded in XHTML.
- Functionality: The
<pattern>element'sidis typically referenced viaurl(#id)infillorstrokeattributes. An invalididmay cause the pattern reference to silently fail, leaving elements unfilled or invisible. - Cross-browser consistency: While some browsers are lenient with invalid XML names, others are not. Using valid names ensures consistent rendering across all browsers and environments.
How to Fix
Rename the id value so it starts with a letter or underscore and contains only valid characters. If you reference this id elsewhere (e.g., in fill="url(#...)" or in CSS), update those references to match.
Examples
❌ Invalid: id starts with a digit
<svgwidth="200"height="200"xmlns="http://www.w3.org/2000/svg">
<defs>
<patternid="1stPattern"width="10"height="10"patternUnits="userSpaceOnUse">
<circlecx="5"cy="5"r="3"fill="blue"/>
</pattern>
</defs>
<rectwidth="200"height="200"fill="url(#1stPattern)"/>
</svg>
❌ Invalid: id starts with a hyphen
<svgwidth="200"height="200"xmlns="http://www.w3.org/2000/svg">
<defs>
<patternid="-stripe-bg"width="10"height="10"patternUnits="userSpaceOnUse">
<rectwidth="5"height="10"fill="red"/>
</pattern>
</defs>
<rectwidth="200"height="200"fill="url(#-stripe-bg)"/>
</svg>
❌ Invalid: id contains special characters
<svgwidth="200"height="200"xmlns="http://www.w3.org/2000/svg">
<defs>
<patternid="my pattern!"width="10"height="10"patternUnits="userSpaceOnUse">
<circlecx="5"cy="5"r="3"fill="green"/>
</pattern>
</defs>
<rectwidth="200"height="200"fill="url(#my pattern!)"/>
</svg>
✅ Valid: id starts with a letter
<svgwidth="200"height="200"xmlns="http://www.w3.org/2000/svg">
<defs>
<patternid="firstPattern"width="10"height="10"patternUnits="userSpaceOnUse">
<circlecx="5"cy="5"r="3"fill="blue"/>
</pattern>
</defs>
<rectwidth="200"height="200"fill="url(#firstPattern)"/>
</svg>
✅ Valid: id starts with an underscore
<svgwidth="200"height="200"xmlns="http://www.w3.org/2000/svg">
<defs>
<patternid="_stripe-bg"width="10"height="10"patternUnits="userSpaceOnUse">
<rectwidth="5"height="10"fill="red"/>
</pattern>
</defs>
<rectwidth="200"height="200"fill="url(#_stripe-bg)"/>
</svg>
✅ Valid: Using letters, digits, hyphens, and underscores
<svgwidth="200"height="200"xmlns="http://www.w3.org/2000/svg">
<defs>
<patternid="dot-grid_v2"width="10"height="10"patternUnits="userSpaceOnUse">
<circlecx="5"cy="5"r="3"fill="green"/>
</pattern>
</defs>
<rectwidth="200"height="200"fill="url(#dot-grid_v2)"/>
</svg>
Note that this same XML 1.0 naming rule applies to id attributes on all SVG elements — not just <pattern>. If you see similar errors on elements like <linearGradient>, <clipPath>, or <filter>, the same fix applies: ensure the id starts with a letter or underscore and uses only valid characters.
The lang attribute on the <html> element declares the primary language of the document. The W3C validator uses heuristics to analyze the text content of your page, and when it detects a mismatch between the declared language and the apparent language, it raises this warning. For example, if your page content is written in English but lang="fr" (French) is set, the validator will flag this inconsistency.
Why This Matters
The lang attribute plays a critical role in several areas:
- Accessibility: Screen readers use the
langattribute to select the correct pronunciation rules and voice profile. If a page is written in English but declares French, a screen reader may attempt to read the content with French pronunciation, making it unintelligible to the user. - Search engines: Search engines use the
langattribute to understand what language a page is in, which affects how the page is indexed and served in search results for different regions. - Browser features: Browsers rely on the
langattribute for built-in translation prompts, spell-checking, hyphenation, and font selection. An incorrect value can cause unexpected behavior in all of these areas.
How to Fix It
- Identify the primary language of your content. Look at the actual text on your page — what language is the majority of it written in?
- Update the
langattribute to the correct BCP 47 language tag for that language (e.g.,enfor English,frfor French,esfor Spanish). - If the
langattribute is already correct and the validator's heuristic is wrong (e.g., your page genuinely is in another language but contains some English text or code), you can safely ignore this warning.
For pages with mixed-language content, set the lang attribute on <html> to the primary language, then use lang attributes on specific elements to mark sections in other languages.
Examples
❌ Incorrect: Content is in English but lang declares French
<!DOCTYPE html>
<htmllang="fr">
<head>
<title>My Website</title>
</head>
<body>
<h1>Welcome to my website</h1>
<p>This is an English paragraph about web development.</p>
</body>
</html>
The validator detects that the content is English, but lang="fr" says it's French.
✅ Fixed: lang attribute matches the content language
<!DOCTYPE html>
<htmllang="en">
<head>
<title>My Website</title>
</head>
<body>
<h1>Welcome to my website</h1>
<p>This is an English paragraph about web development.</p>
</body>
</html>
✅ Mixed-language content with proper lang attributes
If your page is primarily in English but contains sections in another language, set the document language to en and annotate the foreign-language sections individually:
<!DOCTYPE html>
<htmllang="en">
<head>
<title>Language Examples</title>
</head>
<body>
<h1>Welcome</h1>
<p>This page is mostly in English.</p>
<blockquotelang="fr">
<p>Ceci est une citation en français.</p>
</blockquote>
</body>
</html>
This approach ensures screen readers switch pronunciation only for the French <blockquote>, while the rest of the page is correctly read as English.
The X-UA-Compatible header tells Internet Explorer which rendering engine to use for a page. Setting it to IE=edge instructs IE to use the highest available standards mode, ensuring the best compatibility and avoiding legacy rendering quirks. The ,chrome=1 directive was an addition that told browsers with the Google Chrome Frame plugin installed to use Chrome's rendering engine instead of IE's. Google discontinued Chrome Frame in 2014, and the W3C validator only accepts IE=edge as a valid value for this header.
Including the deprecated chrome=1 directive causes a validation error and serves no practical purpose on modern websites. No current browser recognizes or acts on it, so it's dead code that only creates noise in your markup.
The fix is straightforward: remove ,chrome=1 from the content attribute, leaving only IE=edge.
Examples
Incorrect
The following triggers the validation error because of the ,chrome=1 suffix:
<metahttp-equiv="X-UA-Compatible"content="IE=edge,chrome=1">
Correct
Simply use IE=edge as the sole value:
<metahttp-equiv="X-UA-Compatible"content="IE=edge">
Full document example
If you include this meta tag in a complete HTML document, place it early in the <head>:
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<metahttp-equiv="X-UA-Compatible"content="IE=edge">
<title>My Web Page</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
Server-side configuration
If you set X-UA-Compatible as an HTTP response header rather than a meta tag, apply the same fix there. For example, in an Apache .htaccess file:
<IfModule mod_headers.c>
Header set X-UA-Compatible "IE=edge"
</IfModule>
In Nginx:
add_header X-UA-Compatible "IE=edge";
Is this meta tag still needed?
With Internet Explorer reaching end of life, the X-UA-Compatible meta tag itself is largely unnecessary for new projects. If your site no longer needs to support IE, you can safely remove the tag entirely. If you do keep it for legacy support, ensure the value is exactly IE=edge with no additional directives.
Square brackets ([ and ]) are reserved characters under RFC 3986, the standard that defines URI syntax. They are only permitted in the host component of a URL (specifically for IPv6 addresses) and are illegal elsewhere, including the query string. While most browsers are lenient and will load an <iframe> even when the src contains raw brackets, the W3C HTML Validator correctly flags this as an invalid URL value.
This pattern surfaces frequently when working with frameworks or APIs that use bracket notation to represent arrays or nested objects in query parameters — for example, filters[category]=news or items[0]=apple. These URLs work in a browser's address bar because browsers silently fix malformed URLs, but the raw HTML itself is technically non-conforming.
Why it matters
- Standards compliance: A valid HTML document requires all attribute values that expect URLs to contain properly encoded URIs. Raw brackets violate this requirement.
- Interoperability: While mainstream browsers handle this gracefully, other HTML consumers — such as screen readers, web scrapers, embedded webview components, or email clients — may not be as forgiving.
- Maintainability: Properly encoded URLs are unambiguous and reduce the risk of subtle parsing bugs, especially when URLs are constructed dynamically or passed through multiple layers of processing.
How to fix it
There are two main approaches:
Percent-encode the brackets. Replace every
[with%5Band every]with%5Din the URL. The server will decode them back to brackets automatically, so functionality is preserved.Use alternative parameter naming. If you control the server, switch to a naming convention that avoids brackets altogether, such as dot notation (
filters.category) or underscores (filters_category). This keeps the URL valid without any encoding.
If you're generating the URL dynamically in JavaScript, you can use encodeURIComponent() on individual parameter keys and values, or use the URL and URLSearchParams APIs, which handle encoding automatically.
Examples
Incorrect — raw brackets in the query string
<iframesrc="https://example.com/embed?filters[category]=news&filters[tags]=web"></iframe>
The [ and ] characters in the query string make this an invalid URL value, triggering the validator error.
Fixed — percent-encoded brackets
<iframesrc="https://example.com/embed?filters%5Bcategory%5D=news&filters%5Btags%5D=web"></iframe>
Replacing [ with %5B and ] with %5D produces a valid URL. The server receives the same parameter names after decoding.
Fixed — alternative parameter naming
<iframesrc="https://example.com/embed?filters.category=news&filters.tags=web"></iframe>
If the server supports it, using dot notation eliminates the need for brackets entirely, keeping the URL clean and valid.
Generating encoded URLs in JavaScript
<iframeid="content-frame"></iframe>
<script>
consturl=newURL("https://example.com/embed");
url.searchParams.set("filters[category]","news");
url.searchParams.set("filters[tags]","web");
document.getElementById("content-frame").src=url.href;
// Result: https://example.com/embed?filters%5Bcategory%5D=news&filters%5Btags%5D=web
</script>
The URLSearchParams API automatically percent-encodes reserved characters, so you can write the parameter names naturally and let the browser produce a valid URL.
The W3C HTML Validator checks that URLs in href attributes conform to the URL standard (defined by WHATWG). While square brackets are permitted in the host component of a URL (to support IPv6 addresses like [::1]), they are not valid unescaped characters in the query string — the part of the URL that comes after the ?. When the validator encounters a literal [ or ] in the query portion, it flags it as an illegal character.
This issue commonly arises when working with APIs or server-side frameworks that use square brackets in query parameters to represent arrays or nested data structures. For example, PHP-style query strings like ?filter[name]=foo or ?ids[]=1&ids[]=2 contain brackets that must be encoded for valid HTML.
Why this matters
- Standards compliance: The WHATWG URL Standard explicitly lists square brackets among the characters that must be percent-encoded in query strings. Invalid URLs cause W3C validation failures.
- Browser behavior: While most modern browsers are forgiving and will often handle unescaped brackets correctly, relying on this lenient parsing is fragile. Some HTTP clients, proxies, or intermediary servers may reject or mangle URLs with illegal characters.
- Interoperability: Encoded URLs are safer when copied, shared, or processed by tools like link checkers, web scrapers, or email clients that may perform strict URL parsing.
How to fix it
Replace every literal square bracket in the query string with its percent-encoded form:
| Character | Percent-encoded |
|---|---|
[ | %5B |
] | %5D |
If you're generating URLs dynamically in a server-side language or JavaScript, use the appropriate encoding function (e.g., encodeURIComponent() in JavaScript, urlencode() in PHP, or urllib.parse.quote() in Python) to handle this automatically.
Examples
Incorrect: literal brackets in the query string
<ahref="https://example.com/search?filter[status]=active">Active items</a>
This triggers the validation error because [ and ] appear unescaped in the query.
Correct: percent-encoded brackets
<ahref="https://example.com/search?filter%5Bstatus%5D=active">Active items</a>
Replacing [ with %5B and ] with %5D resolves the error. The server receiving this request will decode the values back to filter[status]=active.
Incorrect: array-style parameters with brackets
<ahref="/api/items?ids[]=1&ids[]=2&ids[]=3">Load items</a>
Correct: array-style parameters encoded
<ahref="/api/items?ids%5B%5D=1&ids%5B%5D=2&ids%5B%5D=3">Load items</a>
Note that in addition to encoding the brackets, the & characters in HTML attributes should be written as & for fully valid markup.
Incorrect: brackets in a simple value
<ahref="search.html?q=[value]">Search</a>
Correct: encoded brackets in a simple value
<ahref="search.html?q=%5Bvalue%5D">Search</a>
Note on brackets in the host (valid use)
Square brackets are valid in the host portion of a URL for IPv6 addresses. The following does not trigger the error:
<ahref="http://[::1]:8080/page">IPv6 localhost</a>
The validator only flags brackets that appear in the query string or other parts of the URL where they are not permitted.
The lang attribute on the <html> element tells browsers, search engines, and assistive technologies what language the page content is written in. The validator uses heuristic analysis of the actual text on the page to detect the likely language, and when there's a mismatch, it flags the discrepancy.
Why This Matters
An incorrect lang attribute causes real problems for users and systems that rely on it:
- Screen readers use the
langattribute to select the correct pronunciation engine. A French document marked as English will be read aloud with English pronunciation rules, making it incomprehensible. - Search engines use the language declaration for indexing and serving results to users searching in a specific language.
- Browser features like automatic translation prompts and spell-checking rely on the declared language.
- Hyphenation and typographic rules in CSS also depend on the correct language being declared.
Common Causes
- Copy-pasting a boilerplate — Starting from an English template but writing content in another language without updating
lang. - Multilingual sites — Using the same base template for all language versions without dynamically setting the
langvalue. - Incorrect language subtag — Using the wrong BCP 47 language tag (e.g.,
lang="en"instead oflang="de"for German content).
When You Can Safely Ignore This Warning
This is a warning, not an error. The validator's language detection is heuristic and not always accurate. You may safely ignore it if:
- Your page contains very little text, making detection unreliable.
- The page has significant amounts of content in multiple languages, but the
langattribute correctly reflects the primary language. - The detected language is simply wrong (e.g., short text snippets can confuse the detector).
If you're confident the lang attribute is correct, you can disregard the warning.
How to Fix It
Identify the primary language of your document's content and set the lang attribute to the appropriate BCP 47 language tag. Common tags include en (English), fr (French), de (German), es (Spanish), pt (Portuguese), ja (Japanese), and zh (Chinese).
Examples
Incorrect: Content in French, but lang set to English
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>Mon site</title>
</head>
<body>
<h1>Bienvenue sur notre site</h1>
<p>Nous sommes ravis de vous accueillir sur notre plateforme.</p>
</body>
</html>
This triggers the warning because the validator detects French content but sees lang="en".
Fixed: lang attribute matches the content language
<!DOCTYPE html>
<htmllang="fr">
<head>
<metacharset="utf-8">
<title>Mon site</title>
</head>
<body>
<h1>Bienvenue sur notre site</h1>
<p>Nous sommes ravis de vous accueillir sur notre plateforme.</p>
</body>
</html>
Handling mixed-language content
If your page is primarily in one language but contains sections in another, set the lang attribute on the <html> element to the primary language and use lang on specific elements for the other language:
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>Our Global Site</title>
</head>
<body>
<h1>Welcome to our site</h1>
<p>We are glad you are here.</p>
<blockquotelang="fr">
<p>La vie est belle.</p>
</blockquote>
</body>
</html>
This tells assistive technologies that the page is in English, but the blockquote should be read using French pronunciation rules. The validator should not flag this as a mismatch because the majority of the content is in English.
A URL fragment identifier is the part of a URL that follows the # character. It typically points to an element on the page that has a matching id attribute. According to the URL specification, certain characters — including spaces — are not allowed to appear literally in a URL. When the W3C HTML Validator encounters a raw space in a fragment, it reports this as an illegal character.
This issue matters for several reasons. Browsers may handle unescaped spaces in fragments inconsistently, leading to broken in-page navigation. Screen readers and other assistive technologies rely on well-formed URLs to navigate users to the correct section of a page. Additionally, spaces in id attributes are themselves invalid in HTML — the id attribute must not contain any ASCII whitespace characters. So the root cause often involves two separate violations: an invalid id and an invalid fragment URL.
The best approach is to use hyphens (-) or underscores (_) instead of spaces in your id values, then match the fragment accordingly. This produces clean, readable, and shareable URLs (e.g., page.html#contact-info instead of page.html#contact%20info). If you're working with a CMS or build tool that auto-generates id values with spaces, configure it to produce hyphen-separated, lowercase identifiers instead.
If you absolutely cannot change the id values (e.g., they're generated by a third-party system), you can percent-encode the spaces as %20 in the href. This satisfies URL syntax rules, but note that an id containing spaces is still invalid HTML on its own. Fixing the id is always the preferred solution.
Examples
Invalid: space in fragment and id
This triggers the validator error because href="#My Section" contains an unescaped space. The id="My Section" is also invalid HTML since id values cannot contain spaces.
<ahref="#My Section">Go to section</a>
<h2id="My Section">My Section</h2>
Fixed: hyphen-separated fragment and id
Replace spaces with hyphens in both the id and the fragment. This is the cleanest and most widely recommended approach.
<ahref="#my-section">Go to section</a>
<h2id="my-section">My Section</h2>
Fixed: underscore-separated fragment and id
Underscores work equally well if you prefer that convention.
<ahref="#my_section">Go to section</a>
<h2id="my_section">My Section</h2>
Alternative: percent-encoding the space
Encoding the space as %20 resolves the fragment URL error, but the id with a space is still invalid HTML. Use this only as a last resort when you cannot control the id values.
<ahref="#My%20Section">Go to section</a>
<!-- Note: this id is still invalid HTML due to the space -->
<h2id="My Section">My Section</h2>
Full valid document
A complete example demonstrating multiple in-page links with properly formatted fragments and id values:
<!doctype html>
<htmllang="en">
<head>
<title>Page sections</title>
</head>
<body>
<nav>
<ul>
<li><ahref="#getting-started">Getting Started</a></li>
<li><ahref="#advanced-usage">Advanced Usage</a></li>
<li><ahref="#frequently-asked-questions">FAQ</a></li>
</ul>
</nav>
<h2id="getting-started">Getting Started</h2>
<p>Introduction content here.</p>
<h2id="advanced-usage">Advanced Usage</h2>
<p>Advanced content here.</p>
<h2id="frequently-asked-questions">Frequently Asked Questions</h2>
<p>FAQ content here.</p>
</body>
</html>
The aria-rowspan attribute is used in ARIA-based grid and table structures to indicate how many rows a cell spans. It serves a similar purpose to the rowspan attribute on native HTML <td> and <th> elements, but is designed for custom widgets built with ARIA roles like gridcell, rowheader, and columnheader within grid or treegrid structures.
According to the WAI-ARIA specification, the value of aria-rowspan must be a positive integer — a whole number greater than zero. A value of "0" is invalid because it implies the cell spans no rows, which is semantically meaningless. Note that this differs from native HTML's rowspan attribute, where "0" has a special meaning (span all remaining rows in the row group). The ARIA attribute does not support this behavior.
This matters primarily for accessibility. Screen readers and other assistive technologies rely on aria-rowspan to convey the structure of custom grids to users. An invalid value of "0" can confuse assistive technology, potentially causing it to misrepresent the grid layout or skip the cell entirely. Ensuring valid values helps users who depend on these tools navigate your content correctly.
To fix this issue, determine how many rows the cell actually spans and set aria-rowspan to that number. If the cell occupies a single row, use "1". If it spans multiple rows, use the appropriate count. If you don't need row spanning at all, you can simply remove the aria-rowspan attribute entirely, since the default behavior is to span one row.
Examples
Incorrect: aria-rowspan set to zero
<divrole="grid">
<divrole="row">
<divrole="gridcell"aria-rowspan="0">Name</div>
<divrole="gridcell">Value</div>
</div>
</div>
The value "0" is not a positive integer, so the validator reports an error.
Correct: aria-rowspan set to a positive integer
If the cell spans a single row, use "1" (or remove the attribute, since one row is the default):
<divrole="grid">
<divrole="row">
<divrole="gridcell"aria-rowspan="1">Name</div>
<divrole="gridcell">Value</div>
</div>
</div>
Correct: aria-rowspan for a cell spanning multiple rows
If the cell genuinely spans two rows, set the value accordingly:
<divrole="grid">
<divrole="row">
<divrole="gridcell"aria-rowspan="2">Category</div>
<divrole="gridcell">Item A</div>
</div>
<divrole="row">
<divrole="gridcell">Item B</div>
</div>
</div>
Correct: removing the attribute when no spanning is needed
If the cell doesn't span multiple rows, the simplest fix is to remove aria-rowspan altogether:
<divrole="grid">
<divrole="row">
<divrole="gridcell">Name</div>
<divrole="gridcell">Value</div>
</div>
</div>
When to use aria-rowspan vs. native HTML
If you're building a data table, prefer native HTML <table>, <tr>, <td>, and <th> elements with the standard rowspan attribute. Native table semantics are automatically understood by browsers and assistive technologies without any ARIA attributes. Reserve aria-rowspan for custom interactive widgets (like spreadsheet-style grids or tree grids) where native table elements aren't appropriate. The aria-rowspan value should always match the actual visual and structural layout of your grid to avoid confusing assistive technology users.
A <tr> element already has an implicit ARIA role of row, so adding role="row" is redundant when the parent <table> uses its default semantics or has a role of table, grid, or treegrid.
HTML tables come with built-in accessibility semantics. The <table> element implicitly has role="table", and <tr> implicitly has role="row". Browsers and assistive technologies already understand this structure, so explicitly adding these roles is unnecessary and flagged by the W3C validator.
The only time you'd need to add a role to a <tr> is when the table's native semantics have been overridden — for example, if the <table> has been repurposed with a non-table role like role="presentation" or role="none". In that case, you'd need explicit ARIA roles to restore row semantics.
Incorrect Example
<table>
<trrole="row">
<th>Name</th>
<th>Email</th>
</tr>
<trrole="row">
<td>Alice</td>
<td>alice@example.com</td>
</tr>
</table>
Fixed Example
Simply remove the redundant role="row" from the <tr> elements:
<table>
<tr>
<th>Name</th>
<th>Email</th>
</tr>
<tr>
<td>Alice</td>
<td>alice@example.com</td>
</tr>
</table>
The same fix applies if your <table> explicitly has role="table", role="grid", or role="treegrid" — the <tr> elements still don't need an explicit role="row" because the browser infers it automatically.
A URL follows a specific structure defined by RFC 3986. The general format is:
scheme://host/path?query#fragment
The # character serves as the delimiter that introduces the fragment portion of the URL. It may only appear once in this role. Once the parser encounters the first #, everything after it is treated as the fragment identifier. A second # within that fragment is an illegal character because the fragment production in the URL specification does not permit unescaped # characters.
This validation error commonly arises from:
- Duplicate
#characters — e.g., accidentally including two hash marks like/#?param=value#section. - Misplaced query strings — putting
?key=valueafter the#instead of before it. While this may work in some single-page application routers, it results in the query being part of the fragment, and adding another#after that creates an invalid URL. - Copy-paste errors — assembling URLs from multiple parts and inadvertently introducing an extra
#.
This matters for several reasons. Browsers may handle malformed URLs inconsistently — some may silently truncate or ignore part of the URL, while others may fail to load the resource entirely. The W3C validator flags this because it violates the HTML specification's requirement that the src attribute contain a valid URL. Invalid URLs can also cause issues with assistive technologies, link sharing, and automated tools that parse your HTML.
How to fix it
- Locate all
#characters in the URL and determine which one is the intended fragment delimiter. - Remove any duplicate
#characters that were added by mistake. - Move query parameters before the fragment — the
?queryportion must come before the#fragmentin a well-formed URL. - Percent-encode if needed — if a literal
#must appear as data within the fragment or query value (not as a delimiter), encode it as%23.
Examples
Incorrect: multiple # characters
The second # inside the fragment is illegal:
<iframesrc="https://example.com/#?secret=123#abc"></iframe>
Correct: query before fragment
Move the query string before the # so the URL has a proper structure:
<iframesrc="https://example.com/?secret=123#abc"></iframe>
Correct: single fragment, no query
If you only need a fragment identifier, use a single #:
<iframesrc="https://example.com/#abc"></iframe>
Correct: percent-encoding a literal # in a value
If the fragment itself must contain a # as data (not as a delimiter), percent-encode it:
<iframesrc="https://example.com/?secret=123#color=%23ff0000"></iframe>
Here, %23ff0000 represents the literal string #ff0000 within the fragment value, which is valid because the # is encoded.
Incorrect: hash-based routing with duplicate #
Some single-page app embed URLs use hash-based routing, which can lead to accidental double hashes:
<iframesrc="https://app.example.com/#/dashboard#settings"></iframe>
Correct: use a single fragment for the route
Restructure the URL to use a single # with a combined path:
<iframesrc="https://app.example.com/#/dashboard/settings"></iframe>
Or, if the application supports it, use standard path-based routing instead:
<iframesrc="https://app.example.com/dashboard/settings"></iframe>
The W3C HTML Validator raises this error when it encounters a backslash character (\) inside the href attribute of an anchor element. According to the WHATWG URL Standard, backslashes are not valid characters in URL scheme data. URLs are defined with forward slashes (/) as delimiters — this applies to all parts of a URL, including the scheme, authority, path, query, and fragment.
This issue most commonly occurs when developers copy file paths from Windows operating systems, where backslashes are the default path separator (e.g., C:\Users\Documents\file.html), and paste them directly into HTML markup. It can also happen when server-side code generates URLs using OS-level path functions that produce backslashes on Windows.
Why this matters
- Standards compliance: The WHATWG URL Standard explicitly forbids backslashes in scheme data. Validators flag this as an error because the resulting URL is malformed.
- Cross-browser inconsistency: While some browsers may silently correct backslashes to forward slashes, this behavior is not guaranteed across all browsers or versions. Relying on browser error correction leads to fragile code.
- Broken links: Certain browsers, HTTP clients, or intermediary servers may not auto-correct the backslash, causing the link to fail entirely — resulting in 404 errors or unexpected navigation.
- Security concerns: Backslashes in URLs can be exploited in certain attack vectors like open redirects or path traversal attacks. Using well-formed URLs reduces the attack surface.
How to fix it
- Replace all backslashes (
\) with forward slashes (/) in yourhrefvalues. - Check for URL generation in server-side code. If your application builds URLs programmatically, ensure it uses forward slashes regardless of the host operating system.
- Use relative or absolute URLs consistently. Whether the URL is relative (
images/photo.jpg) or absolute (https://example.com/images/photo.jpg), always use forward slashes.
Examples
Incorrect: backslashes in a relative path
<ahref="pages\about\team.html">Meet the Team</a>
Correct: forward slashes in a relative path
<ahref="pages/about/team.html">Meet the Team</a>
Incorrect: backslashes in an absolute URL
<ahref="https://example.com\blog\2024\post.html">Read the Post</a>
Correct: forward slashes in an absolute URL
<ahref="https://example.com/blog/2024/post.html">Read the Post</a>
Incorrect: Windows file path pasted directly
<ahref="assets\downloads\report.pdf">Download Report</a>
Correct: converted to a proper relative URL
<ahref="assets/downloads/report.pdf">Download Report</a>
Incorrect: mixed slashes
Sometimes a URL contains a mix of forward and backslashes, which also triggers this error:
<ahref="https://example.com/images\photos\sunset.jpg">View Photo</a>
Correct: all forward slashes
<ahref="https://example.com/images/photos/sunset.jpg">View Photo</a>
A quick way to audit your HTML files is to search for \ within any href (or src, action, etc.) attribute values and replace them with /. In most code editors, you can use find-and-replace scoped to attribute values to handle this efficiently.
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