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.
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.
Square brackets ([ and ]) are not valid characters in URLs according to RFC 3986 and must be percent-encoded in the href attribute of <a> elements.
URLs have a strict set of allowed characters. Square brackets are reserved exclusively for IPv6 addresses in the host portion of a URL (e.g., http://[::1]/). Anywhere else in a URL, including query strings, they must be percent-encoded: [ becomes %5B and ] becomes %5D.
This issue commonly appears when linking to URLs that use square brackets in query parameters, a pattern found in some PHP frameworks and APIs (e.g., ?filter[name]=value). While most browsers handle unencoded brackets without problems, the HTML specification requires valid URLs, and the W3C validator flags the violation.
Invalid example
<ahref="https://example.com/results?filter[status]=active&filter[role]=admin">
View results
</a>
Valid example
Replace [ with %5B and ] with %5D:
<ahref="https://example.com/results?filter%5Bstatus%5D=active&filter%5Brole%5D=admin">
View results
</a>
The browser will decode the percent-encoded characters when sending the request, so the server receives the same query string either way.
URLs follow a strict syntax defined by RFC 3986 and the URL Living Standard. Only a specific set of characters are allowed to appear unencoded in a URL. Curly braces ({ and }) are among the characters that fall outside this permitted set. When the W3C validator encounters a raw { or } in an href value, it reports the error: Bad value for attribute "href" on element "a": Illegal character in fragment.
This issue commonly arises in a few scenarios:
- Server-side or client-side template placeholders left unresolved in the rendered HTML (e.g.,
{id},{{slug}}). - URLs copied from API documentation that use curly braces to indicate variable segments (e.g.,
/users/{userId}/posts). - Malformed or auto-generated URLs where curly braces were included by mistake.
Why This Matters
Standards compliance: The HTML specification requires that href values conform to valid URL syntax. Curly braces violate this requirement, producing invalid HTML.
Browser inconsistency: While most modern browsers will attempt to handle URLs with illegal characters by silently encoding them, this behavior is not guaranteed across all browsers or versions. Relying on browser error correction can lead to unpredictable results.
Accessibility and interoperability: Assistive technologies, web crawlers, and other tools that parse HTML may not handle illegal URL characters gracefully. Invalid URLs can break link extraction, bookmarking, and sharing functionality.
Debugging difficulty: If curly braces appear in your rendered HTML, it often signals that a template variable was not properly resolved, which may point to a deeper bug in your application logic.
How to Fix It
The fix depends on why the curly braces are there:
If the curly braces are literal characters that should be part of the URL, replace them with their percent-encoded equivalents:
{becomes%7Band}becomes%7D.If the curly braces are template placeholders (e.g.,
{userId}), ensure your server-side or client-side code resolves them to actual values before the HTML is sent to the browser. The rendered HTML should never contain unresolved template variables.If the curly braces were included by mistake, simply remove them.
Examples
Incorrect: Raw curly braces in href
<ahref="https://example.com/api/users/{userId}/profile">View Profile</a>
This triggers the validation error because { and } are illegal URL characters.
Correct: Percent-encoded curly braces
If the curly braces are meant to be literal parts of the URL:
<ahref="https://example.com/api/users/%7BuserId%7D/profile">View Profile</a>
Correct: Resolved template variable
If the curly braces were template placeholders, ensure your templating engine resolves them before rendering. The final HTML should look like:
<ahref="https://example.com/api/users/42/profile">View Profile</a>
Incorrect: Curly braces in a fragment identifier
<ahref="https://example.com/docs#section-{name}">Jump to Section</a>
Correct: Percent-encoded fragment
<ahref="https://example.com/docs#section-%7Bname%7D">Jump to Section</a>
Incorrect: Curly braces in query parameters
<ahref="https://example.com/search?filter={active}">Active Items</a>
Correct: Percent-encoded query parameter
<ahref="https://example.com/search?filter=%7Bactive%7D">Active Items</a>
Using JavaScript for dynamic URLs
If you need to build URLs dynamically with values that might contain special characters, use encodeURIComponent() in JavaScript rather than inserting raw values into href attributes:
<aid="dynamic-link"href="https://example.com">Dynamic Link</a>
<script>
varvalue="{some-value}";
varlink=document.getElementById("dynamic-link");
link.href="https://example.com/path?param="+encodeURIComponent(value);
</script>
This ensures that any special characters, including curly braces, are automatically percent-encoded in the resulting URL.
A # character inside the fragment portion of a URL is not valid and must be percent-encoded as %23.
The fragment identifier in a URL is everything after the first # character. Because # itself is the delimiter that starts the fragment, any additional # characters within the fragment are illegal according to RFC 3986. Browsers may handle these gracefully, but the markup is technically invalid.
This commonly happens when an id value on the target element contains a #, or when a URL is copied from somewhere and pasted without encoding. The fix is to replace any extra # inside the href fragment with %23. Better yet, avoid using # in id values altogether.
Invalid example
<ahref="#section#2">Go to section</a>
The fragment section#2 contains a second #, which is not allowed.
Fixed example
Option 1: percent-encode the # inside the fragment.
<ahref="#section%232">Go to section</a>
Option 2 (preferred): change the target id so it does not contain # at all.
<h2id="section-2">Section 2</h2>
<ahref="#section-2">Go to section</a>
A space character in the href attribute of a <link> element is not valid in a URL and must be encoded as %20.
URLs follow strict syntax rules defined in RFC 3986. Spaces are not permitted anywhere in a URL, including the query string (the part after the ?). When a URL needs to represent a space, it must be percent-encoded as %20.
Browsers are forgiving and will often handle spaces by silently encoding them, but the HTML is still technically invalid. This can lead to unexpected behavior in less forgiving environments like HTML emails, web crawlers, or certain HTTP clients.
Invalid Example
<linkrel="stylesheet"href="https://example.com/styles?family=Open Sans">
Fixed Example
Replace every space with %20:
<linkrel="stylesheet"href="https://example.com/styles?family=Open%20Sans">
If your URL has multiple spaces or special characters, make sure each one is properly percent-encoded. Common replacements include %20 for spaces, %26 for & inside already-encoded contexts, and %3D for =. Most programming languages offer a URL-encoding function (e.g., encodeURI() in JavaScript) to handle this automatically.
A | (pipe) character in a link element's href attribute is not valid in a URL because it is not a legal character in a query string without being percent-encoded.
This error commonly appears when loading multiple Google Fonts in a single <link> tag using the older Google Fonts API v1 syntax, which used the pipe character to separate font families:
fonts.googleapis.com/css?family=Open+Sans|Roboto
The | character must be encoded as %7C to be valid in a URL. However, even with encoding, the Google Fonts API v1 no longer supports loading multiple families this way. The current Google Fonts API v2 (/css2) uses separate family parameters instead.
Bad example
<linkrel="stylesheet"
href="https://fonts.googleapis.com/css?family=Open+Sans|Roboto">
Good example
Use the Google Fonts API v2 with separate family parameters:
<linkrel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Open+Sans&family=Roboto&display=swap">
If you are not using Google Fonts and cannot change the URL structure, percent-encode the pipe character:
<linkrel="stylesheet"
href="https://example.com/css?param1=a%7Cparam2=b">
When the browser's HTML parser encounters a src attribute, it expects the value between the opening and closing quotes to be a valid URL. URLs have strict rules about which characters are permitted — a literal double quote (") is not one of them. If a " character appears inside the URL, the validator flags it as an illegal character in the query string (or other parts of the URL).
This error often doesn't stem from an intentionally embedded quote in the URL itself. Instead, it's usually a symptom of malformed HTML around the attribute. Common causes include:
- Stray quotes after the attribute value, such as writing
src="https://example.com/script.js"async""instead of properly separating attributes. - Accidentally doubled closing quotes, like
src="https://example.com/script.js"". - Boolean attributes given empty-string values incorrectly, such as
async""instead of justasync, which causes the parser to interpret the extra quotes as part of the precedingsrcvalue. - Copy-paste errors that introduce smart quotes (
"") or extra quotation marks into the URL.
This matters for several reasons. Malformed src attributes can cause the script to fail to load entirely, breaking functionality on your page. It also violates the HTML specification, which can lead to unpredictable behavior across different browsers as each parser tries to recover from the error differently.
To fix the issue, carefully inspect the <script> tag and ensure that:
- The
srcattribute value contains only a valid URL with no unescaped"characters. - The opening and closing quotes around the attribute value are properly balanced.
- Attributes are separated by whitespace — not jammed together.
- Boolean attributes like
asyncanddeferare written as bare keywords without values.
If you genuinely need a double quote character in a query string (which is rare), encode it as %22.
Examples
Incorrect — stray quotes between attributes
In this example, the async attribute is malformed as async"", which causes the parser to interpret the extra quotes as part of the src URL:
<scriptsrc="https://example.com/js/app.js?ver=3.1"async""></script>
Incorrect — doubled closing quote
Here, an extra " at the end of the src value creates an illegal character in the URL:
<scriptsrc="https://example.com/js/app.js?ver=3.1""></script>
Incorrect — smart quotes from copy-paste
Curly/smart quotes copied from a word processor are not valid HTML attribute delimiters and get treated as part of the URL:
<scriptsrc="https://example.com/js/app.js?ver=3.1"></script>
Correct — clean attributes with proper quoting
<scriptsrc="https://example.com/js/app.js?ver=3.1"async></script>
Correct — boolean attribute written as a bare keyword
Boolean attributes like async and defer don't need a value. Simply include the attribute name:
<scriptsrc="https://example.com/js/app.js?ver=3.1"defer></script>
While async="async" is technically valid per the spec, the cleanest and most common form is the bare attribute. Avoid empty-string patterns like async="" placed without proper spacing, as they can lead to the exact quoting errors described here.
Correct — full valid document
<!DOCTYPE html>
<htmllang="en">
<head>
<title>Valid Script Example</title>
<scriptsrc="https://example.com/js/app.js?ver=3.1"async></script>
</head>
<body>
<p>Page content here.</p>
</body>
</html>
If you're generating <script> tags dynamically (through a CMS, template engine, or build tool), check the template source rather than just the rendered output. The stray quotes are often introduced by incorrect string concatenation or escaping logic in the server-side code.
The linear-gradient() function went through several syntax revisions during CSS standardization. Early drafts and vendor-prefixed implementations (like -webkit-linear-gradient()) used bare direction keywords such as top, bottom left, etc., where the keyword indicated the starting point of the gradient. The final standard, defined in the CSS Images Module Level 3 and Level 4 specifications, changed this so that direction keywords use the to prefix and indicate the ending point of the gradient. For example, the old linear-gradient(top, #fff, #000) meant "start at the top and go to the bottom," while the correct modern equivalent is linear-gradient(to bottom, #fff, #000).
This matters because the old syntax without to is not valid CSS per the current specification. While some browsers may still interpret the legacy syntax for backward compatibility, relying on it is risky — behavior can vary across browsers, and it will trigger validation errors. Using standard-compliant CSS ensures consistent rendering and forward compatibility.
How to fix it
Replace the bare direction keyword with the correct to syntax. Note that the direction meaning is inverted: the old syntax specified where the gradient starts, while the new syntax specifies where it goes to.
Here's a quick mapping from old to new syntax:
| Old (invalid) | New (valid) | Angle equivalent |
|---|---|---|
top | to bottom | 180deg |
bottom | to top | 0deg |
left | to right | 90deg |
right | to left | 270deg |
top left | to bottom right | N/A (use to syntax) |
Important: Notice that top in the old syntax means "start at top, go to bottom." So the modern equivalent is to bottom, not to top. If the validator message says the argument should be to top, it means you wrote top — but be sure you understand which direction your gradient should actually go before blindly replacing it. If you truly want the gradient to go toward the top, use to top. If you want it to go from the top downward, use to bottom.
If you don't specify a direction at all, linear-gradient() defaults to to bottom (top-to-bottom), which is often what you want.
Examples
Invalid: bare direction keyword
<divstyle="background:linear-gradient(top,#ffffff,#000000);">
Content
</div>
The bare keyword top is not valid in the standard linear-gradient() syntax and will trigger the validator error.
Fixed: using the to keyword
<divstyle="background:linear-gradient(to bottom,#ffffff,#000000);">
Content
</div>
Since the old top meant "start at the top," the equivalent standard syntax is to bottom.
Fixed: using an angle
<divstyle="background:linear-gradient(180deg,#ffffff,#000000);">
Content
</div>
An angle of 180deg produces the same top-to-bottom gradient.
Full document example
<!DOCTYPE html>
<htmllang="en">
<head>
<title>Gradient Example</title>
<style>
.box{
width:200px;
height:100px;
/* Valid: direction keyword with "to" */
background:linear-gradient(to top,#ffffff,#000000);
}
.box-angle{
width:200px;
height:100px;
/* Valid: angle equivalent of "to top" */
background:linear-gradient(0deg,#ffffff,#000000);
}
.box-default{
width:200px;
height:100px;
/* Valid: no direction specified, defaults to "to bottom" */
background:linear-gradient(#ffffff,#000000);
}
</style>
</head>
<body>
<divclass="box"></div>
<divclass="box-angle"></div>
<divclass="box-default"></div>
</body>
</html>
All three approaches are valid. Choose whichever is clearest for your use case — the to keyword syntax is generally the most readable, while angles offer more precision for diagonal or non-cardinal directions.
The W3C HTML validator raises this error when the src attribute of an <img> element contains characters that are not permitted in a valid URI. The most common culprit is the < character, but other characters like >, {, }, |, \, ^, and backticks are also illegal in URIs according to RFC 3986.
This issue typically occurs in a few common scenarios:
- Template syntax left unresolved: Server-side or client-side template tags (e.g.,
<%= imageUrl %>,{{ image.src }}) appear literally in the HTML output instead of being processed into actual URLs. - Copy-paste errors: HTML markup or angle brackets accidentally end up inside a
srcvalue. - Malformed dynamic URLs: JavaScript or server-side code incorrectly constructs a URL that includes raw HTML or special characters.
This matters because browsers may fail to load the image or interpret the URL unpredictably. Invalid URIs can also cause issues with screen readers and assistive technologies that try to resolve the src to provide context about the image. Keeping your markup standards-compliant ensures consistent behavior across all browsers and environments.
How to fix it
- Check for unprocessed template tags. If you see template syntax like
<%,{{, or similar in the rendered HTML, ensure your templating engine is running correctly and outputting the resolved URL. - Use valid, well-formed URLs. The
srcvalue should be a properly formatted absolute or relative URL. - Percent-encode special characters. If a special character is genuinely part of the URL (which is rare for
<), encode it:<becomes%3C,>becomes%3E, and so on. - Inspect your generated HTML. View the page source in your browser to confirm the actual output, rather than relying on what your code looks like before processing.
Examples
Incorrect — template syntax in src
The template tag was not processed, leaving a < character in the src attribute:
<imgsrc="<%= user.avatar %>"alt="User avatar">
Incorrect — HTML accidentally inside src
Angle brackets from stray markup ended up in the URL:
<imgsrc="images/<thumbnail>/photo.jpg"alt="Photo">
Correct — a valid relative URL
<imgsrc="images/photo.jpg"alt="Photo">
Correct — a valid absolute URL
<imgsrc="https://example.com/images/photo.jpg"alt="Photo">
Correct — special characters percent-encoded
If the URL genuinely requires characters that are not allowed in a URI, percent-encode them:
<imgsrc="https://example.com/search?q=a%3Cb"alt="Search result">
In this case, %3C represents the < character in the query string, making the URI valid.
Private Use Area (PUA) characters are reserved ranges in Unicode whose interpretation is not specified by any encoding standard. Their meaning is determined entirely by private agreement between cooperating parties—such as a font vendor and its users. This means that a PUA character that renders as a custom icon in one font may appear as a blank square, a question mark, or a completely different glyph when that specific font is unavailable.
This warning commonly appears when using icon fonts like older versions of Font Awesome, Material Icons, or custom symbol fonts. These fonts map their icons to PUA code points. While this approach works visually when the font loads correctly, it creates several problems:
- Accessibility: Screen readers cannot interpret PUA characters meaningfully. A visually impaired user may hear nothing, hear "private use area character," or hear an unrelated description depending on their assistive technology.
- Portability: If the associated font fails to load (due to network issues, content security policies, or user preferences), the characters become meaningless boxes or blank spaces.
- Interoperability: Copy-pasting text containing PUA characters into another application, email client, or document will likely produce garbled or missing content since the receiving system won't know how to interpret those code points.
- Standards compliance: The W3C and Unicode Consortium both recommend against using PUA characters in publicly exchanged documents for exactly these reasons.
Sometimes PUA characters sneak into your HTML unintentionally—through copy-pasting from word processors, PDFs, or design tools that use custom encodings. Other times, they are inserted deliberately via CSS content properties or HTML entities by icon font libraries.
To fix this, identify where the PUA characters appear and replace them with standard alternatives. Use inline SVG for icons, standard Unicode symbols where appropriate (e.g., ✓ U+2713 instead of a PUA checkmark), or CSS background images. If you must use an icon font, hide the PUA character from assistive technology using aria-hidden="true" and provide an accessible label separately.
Examples
Problematic: PUA character used directly in HTML
<!-- The character below (U+E001) is a PUA code point -->
<p>Status: </p>
Without the specific icon font loaded, PUA characters like U+E001 render as missing glyphs or blank spaces.
Fixed: Using inline SVG with accessible label
<p>
Status:
<svgaria-hidden="true"width="16"height="16"viewBox="0 0 16 16">
<pathd="M6 10.8L2.5 7.3 1.1 8.7 6 13.6 14.9 4.7 13.5 3.3z"/>
</svg>
<span>Complete</span>
</p>
Problematic: Icon font via CSS content property
<style>
.icon-check::before{
font-family:"MyIcons";
content:"\e001";/* PUA character */
}
</style>
<spanclass="icon-check"></span>
Fixed: Icon font with accessibility safeguards
If you must continue using an icon font, hide the PUA character from assistive technology and provide an accessible alternative:
<style>
.icon-check::before{
font-family:"MyIcons";
content:"\e001";
}
</style>
<spanclass="icon-check"aria-hidden="true"></span>
<spanclass="sr-only">Checkmark</span>
Note that this approach still triggers the validator warning if the PUA character is detectable in the markup. The most robust fix is to avoid PUA characters entirely.
Fixed: Using a standard Unicode character
<p>Status: ✓ Complete</p>
The character ✓ (U+2713, CHECK MARK) is a standard Unicode character that is universally understood and renders consistently across platforms.
Problematic: PUA character from copy-paste
<p>Click here to download</p>
Invisible or unexpected PUA characters sometimes hide in text pasted from external sources. Inspect your source code carefully—many code editors can highlight non-ASCII characters or reveal their code points.
Fixed: Cleaned-up text
<p>Click here to download</p>
If you've audited your document and determined that the PUA characters are intentional and rendering correctly in your target environments, you may choose to accept this warning. However, for publicly accessible web pages, replacing PUA characters with standard alternatives is always the safer and more accessible choice.
A URL fragment identifier is the part of a URL that follows the # symbol, typically used to link to a specific section within a page. The URL specification (RFC 3986 and the WHATWG URL Standard) defines a strict set of characters that are permitted in fragments. The > character (greater-than sign) is among those that are not allowed to appear literally. When the W3C HTML Validator encounters > inside an href value — whether in the fragment or elsewhere in the URL — it raises this error.
In most cases, the > character appears in a URL by accident — for example, from a copy-paste error, a typo where the closing > of the tag accidentally ends up inside the attribute value, or a malformed template expression. Less commonly, a developer may genuinely need the > character as part of the URL content.
Why This Matters
- Broken links: Browsers may interpret the
>as the end of the HTML tag or handle the URL unpredictably, leading to broken navigation. - Standards compliance: Invalid URLs violate both the HTML specification and URL syntax standards, causing validation failures.
- Accessibility: Screen readers and other assistive technologies rely on well-formed markup. A malformed
hrefcan confuse these tools and prevent users from reaching the intended destination. - Interoperability: While some browsers may silently correct or ignore the invalid character, others may not. Valid URLs guarantee consistent behavior everywhere.
How to Fix It
- If the
>is a typo or copy-paste error, simply remove it from thehrefvalue. This is the most common scenario. - If the
>is intentionally part of the fragment or URL, replace it with its percent-encoded equivalent:%3E. - Review your templates or CMS output — this error often originates from template engines or content management systems that inject malformed values into
hrefattributes.
Examples
Accidental > in the URL path
A common mistake is accidentally including the tag's closing > inside the attribute value:
<!-- ❌ Invalid: ">" is not allowed in the URL -->
<ahref="/page.php>">Read more</a>
Remove the stray >:
<!-- ✅ Valid: clean URL without illegal character -->
<ahref="/page.php">Read more</a>
Illegal > in a fragment identifier
The > can also appear inside the fragment portion of the URL:
<!-- ❌ Invalid: ">" in the fragment -->
<ahref="/docs#section->overview">Go to overview</a>
If the > is unintentional, remove it:
<!-- ✅ Valid: fragment without illegal character -->
<ahref="/docs#section-overview">Go to overview</a>
If the > is genuinely needed in the fragment, percent-encode it:
<!-- ✅ Valid: ">" encoded as %3E -->
<ahref="/docs#section-%3Eoverview">Go to overview</a>
Query string or path containing >
The same rule applies outside of fragments. If > appears anywhere in the URL, encode it:
<!-- ❌ Invalid: ">" in query parameter -->
<ahref="/search?filter=price>100">Expensive items</a>
<!-- ✅ Valid: ">" percent-encoded as %3E -->
<ahref="/search?filter=price%3E100">Expensive items</a>
Template output errors
If you are using a server-side template or JavaScript framework, ensure that dynamic values inserted into href are properly URL-encoded. For example, in a template that generates links:
<!-- ❌ Invalid: unencoded dynamic value -->
<ahref="/results#filter=>50">View results</a>
<!-- ✅ Valid: properly encoded value -->
<ahref="/results#filter=%3E50">View results</a>
Most server-side languages provide built-in URL encoding functions (e.g., encodeURIComponent() in JavaScript, urlencode() in PHP, urllib.parse.quote() in Python) that will handle this automatically. Always use these functions when inserting dynamic content into URLs.
A | (pipe) character in the fragment portion of a URL is not allowed without percent-encoding it as %7C.
The fragment is the part of a URL that comes after the # symbol. According to RFC 3986, fragments can only contain unreserved characters (A-Z, a-z, 0-9, -, ., _, ~), percent-encoded characters, sub-delimiters (!, $, &, ', (, ), *, +, ,, ;, =), :, @, /, and ?.
The pipe character | is not in any of those sets, so it must be percent-encoded as %7C when used in a URL fragment.
This issue commonly appears when fragment identifiers are auto-generated from headings or content that contains |, such as table-related text or wiki-style page anchors.
Invalid example
<ahref="/page#section|intro">Go to section</a>
Valid example
Replace | with its percent-encoded equivalent %7C:
<ahref="/page#section%7Cintro">Go to section</a>
If you control the target element's id, a better approach is to remove the pipe character from the id altogether and use only unreserved characters:
<h2id="section-intro">Section intro</h2>
<ahref="/page#section-intro">Go to section</a>
The ping attribute specifies a space-separated list of URLs that the browser should notify (via a small POST request) when a user follows a hyperlink. This is commonly used for click tracking and analytics. According to the HTML specification, every URL in the list must be a valid, non-empty URL that uses either the http or https scheme — no other schemes or relative paths are permitted.
This restriction exists for practical and security reasons. The ping mechanism is specifically designed for web-based tracking endpoints, so only web protocols make sense. Relative URLs are disallowed because the ping is sent as a separate request independent of normal navigation, and the specification requires absolute URLs to unambiguously identify the target server. Using invalid values won't produce the intended tracking behavior and will cause the browser to silently ignore the ping attribute entirely.
From an accessibility and standards compliance standpoint, ensuring valid ping values means your analytics will work reliably in browsers that support the attribute. Note that browser support varies — some browsers (notably Firefox) disable ping by default or hide it behind a preference — so you should not rely on it as your sole tracking mechanism.
How to fix it
- Replace relative URLs with absolute URLs. If you have a value like
/trackortrack.php, prepend the full origin (e.g.,https://example.com/track). - Remove non-HTTP schemes. Values like
mailto:someone@example.comorftp://example.com/logare not valid forping. - Ensure each URL in the list is properly formatted. Multiple URLs must be separated by spaces (not commas or semicolons), and each one must be a complete
httporhttpsURL.
Examples
Incorrect: relative URL
<ahref="https://example.com"ping="/track">Visit Example</a>
The value /track is a relative URL, which is not allowed in the ping attribute.
Incorrect: unsupported scheme
<ahref="https://example.com"ping="ftp://example.com/log">Visit Example</a>
The ftp: scheme is not permitted — only http and https are valid.
Incorrect: comma-separated URLs
<ahref="https://example.com"ping="https://example.com/track, https://analytics.example.com/log">Visit Example</a>
Multiple URLs must be space-separated, not comma-separated. The commas make each URL invalid.
Correct: single absolute URL
<ahref="https://example.com"ping="https://example.com/track">Visit Example</a>
Correct: multiple space-separated absolute URLs
<ahref="https://example.com"ping="https://example.com/track https://analytics.example.com/log">Visit Example</a>
Each URL is a fully qualified https URL, and they are separated by a single space. Both will receive a POST request when the link is clicked (in browsers that support the ping attribute).
The | (pipe) character is not allowed in URLs and must be percent-encoded as %7C in the href attribute of an <a> element.
URLs follow the syntax rules defined in RFC 3986. The pipe character is not among the allowed characters in the query component of a URI. When a URL contains a literal |, browsers will often handle it gracefully, but the HTML is technically invalid. The fix is to replace every | with its percent-encoded equivalent %7C.
This commonly appears when linking to external APIs, legacy systems, or content management platforms that use | as a delimiter in query strings.
HTML examples
Invalid example
<ahref="https://example.com/search?filters=color|size|price">Search</a>
Valid example
<ahref="https://example.com/search?filters=color%7Csize%7Cprice">Search</a>
If the URLs are generated dynamically in JavaScript, encodeURI() or encodeURIComponent() will handle the encoding automatically:
constfilters="color|size|price";
consturl=`https://example.com/search?filters=${encodeURIComponent(filters)}`;
// Result: https://example.com/search?filters=color%7Csize%7Cprice
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