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.
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 URL can have only one fragment identifier — the portion that comes after the single # symbol. When the browser encounters a # in a URL, it treats everything after it as the fragment. If a second # appears, it becomes an illegal character within that fragment because the fragment portion of a URL does not allow unencoded # characters. This violates the URL specification (WHATWG) and the HTML standard's requirements for valid URLs in the href attribute.
This matters for several reasons:
- Standards compliance: Browsers may handle malformed URLs inconsistently, leading to unpredictable navigation behavior across different environments.
- Accessibility: Screen readers and assistive technologies rely on well-formed URLs to correctly announce link destinations and allow users to navigate.
- SEO and crawling: Search engine crawlers may fail to follow or index pages with invalid URLs.
The # character is perfectly valid when used exactly once to introduce a fragment. For example, #pricing or /faqs#pricing are fine. The issue arises when a second # appears, such as /faqs#guarantee#pricing, or when # is used in a part of the URL where it isn't expected.
If you genuinely need a literal # character as part of a path or query string (not as a fragment delimiter), you must percent-encode it as %23.
Examples
❌ Invalid: Multiple # characters in the URL
This triggers the validator error because the fragment (guarantee#pricing) contains an illegal #:
<ahref="/faqs#guarantee#pricing">Guarantee and Pricing</a>
✅ Fixed: Use a single fragment identifier
Link to one section at a time with a single #:
<ahref="/faqs#guarantee">Guarantee</a>
<ahref="/faqs#pricing">Pricing</a>
✅ Fixed: Encode the # if it's meant as a literal character
If the # is part of the actual path or data (not a fragment delimiter), encode it as %23:
<ahref="/faqs%23guarantee#pricing">Pricing</a>
In this case, /faqs%23guarantee is the path (containing a literal #), and pricing is the fragment.
Using fragments correctly in a table of contents
Fragments work well for in-page navigation. Each target element needs a matching id:
<nav>
<ul>
<li><ahref="#pricing">Pricing</a></li>
<li><ahref="#terms">Terms</a></li>
<li><ahref="#guarantee">Guarantee</a></li>
</ul>
</nav>
<h2id="pricing">Pricing</h2>
<p>All about pricing...</p>
<h2id="terms">Terms</h2>
<p>You can find our terms at...</p>
<h2id="guarantee">Guarantee</h2>
<p>We offer a guarantee...</p>
❌ Invalid: # in a query string or path without encoding
Sometimes this error appears when a URL accidentally includes an unencoded # in the wrong place:
<ahref="/search?color=#ff0000">Red items</a>
✅ Fixed: Encode the # in the query value
<ahref="/search?color=%23ff0000">Red items</a>
Here, %23ff0000 correctly represents the literal string #ff0000 as a query parameter value, rather than being misinterpreted as the start of a fragment.
Common causes
- Copy-pasting URLs from other sources that contain multiple
#characters. - Dynamically generated links where fragment values aren't properly sanitized.
- Color codes in URLs like
#ff0000that need to be percent-encoded as%23ff0000. - Concatenation errors in templates where a
#is added both in the base URL and the appended fragment.
The fix is always the same: make sure your href contains at most one # used as the fragment delimiter, and percent-encode (%23) any # that is meant as a literal character.
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.
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.
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 imagesrcset attribute is used exclusively on <link> elements that have rel="preload" and as="image". It mirrors the srcset attribute of the <img> element, allowing the browser to preload the most appropriate image resource based on the current viewport and display conditions. When the validator encounters imagesrcset="" (an empty value), it reports this error because an empty string is not a valid source set — it must contain at least one image candidate string.
Each image candidate string in the imagesrcset value consists of a URL followed by an optional width descriptor (e.g., 480w) or pixel density descriptor (e.g., 2x). Multiple candidates are separated by commas. This is the same syntax used by the srcset attribute on <img> elements.
This issue typically arises when a CMS, static site generator, or templating engine outputs the imagesrcset attribute with an empty value — for example, when a responsive image field has no data. Browsers may ignore the malformed attribute, but it results in invalid HTML, can cause unexpected preloading behavior, and signals that the page's resource hints are misconfigured. Fixing it ensures standards compliance and that the browser's preload scanner works as intended.
How to fix it
- Provide a valid source set — populate
imagesrcsetwith one or more image candidate strings. - Remove the attribute — if you don't have multiple image sources to preload, remove
imagesrcset(andimagesizes) from the<link>element entirely. You can still preload a single image using just thehrefattribute. - Conditionally render — if your templating system might produce an empty value, add logic to omit the attribute when no responsive sources are available.
When using imagesrcset, you should also include the imagesizes attribute (mirroring the sizes attribute on <img>) so the browser can select the correct candidate based on layout information.
Examples
❌ Empty imagesrcset triggers the error
<linkrel="preload"as="image"href="hero.jpg"imagesrcset=""imagesizes="">
The empty imagesrcset value is invalid and produces the W3C validation error.
✅ Valid imagesrcset with width descriptors
<link
rel="preload"
as="image"
href="hero-800.jpg"
imagesrcset="hero-400.jpg 400w, hero-800.jpg 800w, hero-1200.jpg 1200w"
imagesizes="(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px">
This tells the browser to preload the most appropriate image based on the viewport width, matching the responsive behavior of the corresponding <img> element on the page.
✅ Valid imagesrcset with pixel density descriptors
<link
rel="preload"
as="image"
href="logo.png"
imagesrcset="logo.png 1x, logo@2x.png 2x">
This preloads the correct logo variant based on the device's pixel density.
✅ Removing the attribute when no responsive sources exist
<linkrel="preload"as="image"href="hero.jpg">
If you only have a single image to preload, simply use href without imagesrcset. This is valid and avoids the error entirely.
✅ Conditional rendering in a template
If you're using a templating language, conditionally include the attribute:
<!-- Pseudocode example -->
<link
rel="preload"
as="image"
href="hero.jpg"
{%ifresponsive_sources%}
imagesrcset="{{ responsive_sources }}"
imagesizes="{{ image_sizes }}"
{%endif%}>
This prevents the attribute from being rendered with an empty value when no responsive image data is available.
In URLs, certain characters must be percent-encoded — a process where a character is replaced by % followed by exactly two hexadecimal digits representing its byte value. For example, a space becomes %20, a hash becomes %23, and an ampersand becomes %26. The % character itself is the escape prefix, so when a URL parser encounters %, it expects the next two characters to be valid hexadecimal digits (0–9, A–F, a–f). If they aren't, the URL is malformed.
This validation error typically arises in a few scenarios:
- A literal
%is used in the URL without encoding. For instance, a filename or path segment contains a%sign that wasn't converted to%25. - An incomplete or corrupted percent-encoding sequence. Someone may have partially encoded a URL, leaving behind sequences like
%G5or%2that don't form valid two-digit hex codes. - Copy-paste errors. A URL was pasted from a source that stripped characters or introduced stray
%symbols.
This matters because browsers may interpret malformed URLs inconsistently. One browser might try to "fix" the URL by encoding the stray %, while another might pass it through as-is, leading to broken form submissions or unexpected server-side behavior. Ensuring valid URLs in the action attribute guarantees predictable behavior across all browsers and complies with both the WHATWG URL Standard and the HTML specification.
How to Fix
- Locate every
%in the URL. Check whether each%is followed by exactly two hexadecimal digits (e.g.,%20,%3A,%7E). - Encode literal
%characters. If a%is meant to appear as a literal character in the URL (not as part of a percent-encoding sequence), replace it with%25. - Fix incomplete sequences. If a sequence like
%2or%GZexists, determine the intended character and encode it correctly, or remove the stray%. - Use proper encoding tools. In JavaScript, use
encodeURIComponent()orencodeURI()to safely encode URLs. Most server-side languages have equivalent functions (e.g.,urlencode()in PHP,urllib.parse.quote()in Python).
Examples
Literal % not encoded
This triggers the error because %d is not a valid percent-encoding sequence (d is only one character, and what follows may not be hex):
<!-- ❌ Bad: bare % not followed by two hex digits -->
<formaction="/search?discount=20%off">
<buttontype="submit">Search</button>
</form>
Encode the % as %25:
<!-- ✅ Good: literal % encoded as %25 -->
<formaction="/search?discount=20%25off">
<buttontype="submit">Search</button>
</form>
Incomplete percent-encoding sequence
Here, %2 is missing its second hex digit:
<!-- ❌ Bad: %2 is an incomplete sequence -->
<formaction="/path/to%2file.html">
<buttontype="submit">Submit</button>
</form>
If the intent was to encode a / character (%2F), complete the sequence:
<!-- ✅ Good: complete percent-encoding for "/" -->
<formaction="/path/to%2Ffile.html">
<buttontype="submit">Submit</button>
</form>
Invalid hex characters after %
The characters G and Z are not hexadecimal digits:
<!-- ❌ Bad: %GZ is not valid hex -->
<formaction="/data%GZprocess">
<buttontype="submit">Go</button>
</form>
If %GZ was never intended as encoding, escape the % itself:
<!-- ✅ Good: literal % properly encoded -->
<formaction="/data%25GZprocess">
<buttontype="submit">Go</button>
</form>
Using JavaScript to encode safely
When building URLs dynamically, use built-in encoding functions to avoid this issue entirely:
constquery="20% off";
constsafeURL="/search?q="+encodeURIComponent(query);
// Result: "/search?q=20%25%20off"
This ensures every special character — including % — is properly percent-encoded before it's placed in the action attribute.
The HTML specification restricts what can appear inside a comment. Specifically, a comment must not contain the string -- (two consecutive hyphens) except as part of the opening and closing delimiters. This rule originates from SGML and XML 1.0, where -- is treated as a comment delimiter, and having extra occurrences inside the comment body creates ambiguity about where the comment starts and ends.
While most modern browsers are lenient and will handle comments with double hyphens without issues, the markup is technically invalid. This matters for several reasons:
- XML compatibility: If your HTML is served as XHTML or processed by XML parsers, double hyphens inside comments will cause parsing errors and potentially break the entire document.
- Standards compliance: The HTML living standard explicitly states that comments must not contain
--, so validators flag this as an error. - Predictable parsing: Different parsers may interpret malformed comments differently, leading to inconsistent behavior across tools, crawlers, and assistive technologies.
To fix the issue, look inside your comment text and replace any occurrence of -- with something else, such as a single hyphen, an equals sign, or a different separator.
Examples
Incorrect: double hyphens inside the comment body
<!-- This is a separator ---------- end of section -->
The string of hyphens inside the comment contains multiple consecutive -- pairs, making the comment invalid.
<!-- Do not use -- as a separator -->
Here, -- appears literally in the comment text, which violates the rule.
<!-- TODO -- fix this later -- maybe next sprint -->
Multiple -- sequences appear as casual text separators, each one triggering the validation error.
Correct: avoiding double hyphens
Replace double hyphens with a different character or pattern:
<!-- This is a separator ========== end of section -->
<!-- Do not use dashes as a separator -->
<!-- TODO: fix this later, maybe next sprint -->
Correct: standard comment syntax
Simple comments that don't contain -- in their body are perfectly valid:
<!-- This is a valid comment -->
<!-- Multi-line comments are fine too, as long as they don't contain double hyphens. -->
Multi-line comments are fine too,
as long as they don't contain double hyphens.
-->
Common pitfall: decorative comment borders
Developers sometimes use hyphens to create visual separators in their source code:
<!-- -------------------------------- -->
<!-- Navigation Section -->
<!-- -------------------------------- -->
Replace these with a different character:
<!-- ================================ -->
<!-- Navigation Section -->
<!-- ================================ -->
Or simply remove the decorative lines:
<!-- Navigation Section -->
The tabindex attribute controls whether and in what order an element can receive keyboard focus. The W3C validator reports this error when it encounters tabindex="" because the HTML specification requires the attribute's value to be a valid integer — and an empty string cannot be parsed as one. This commonly happens when a value is accidentally omitted, when a template engine outputs a blank value, or when a CMS generates the attribute without a proper default.
Why this matters
Keyboard navigation is fundamental to web accessibility. Screen readers and assistive technologies rely on tabindex values to determine focus behavior. An empty tabindex attribute creates ambiguity: browsers may ignore it or handle it inconsistently, leading to unpredictable focus behavior for keyboard and assistive technology users. Beyond accessibility, it also means your HTML is invalid according to the WHATWG HTML living standard, which strictly defines tabindex as accepting only a valid integer.
How tabindex works
The attribute accepts an integer value with three meaningful ranges:
- Negative value (e.g.,
tabindex="-1"): The element can be focused programmatically (via JavaScript's.focus()), but it is excluded from the sequential keyboard tab order. tabindex="0": The element is added to the natural tab order based on its position in the document source. This is the most common way to make non-interactive elements (like a<div>or<span>) keyboard-focusable.- Positive value (e.g.,
tabindex="1",tabindex="5"): The element is placed in the tab order ahead of elements withtabindex="0"or notabindex, with lower numbers receiving focus first. Using positive values is generally discouraged because it overrides the natural document order and makes focus management harder to maintain.
How to fix it
- If the element should be focusable in tab order, set
tabindex="0". - If the element should only be focusable programmatically, set
tabindex="-1". - If you don't need custom focus behavior, remove the
tabindexattribute entirely. Interactive elements like<a>,<button>, and<input>are already focusable by default. - If a template or CMS generates the attribute, ensure the logic provides a valid integer or omits the attribute when no value is available.
Examples
❌ Empty tabindex value (triggers the error)
<divtabindex="">Click me</div>
<buttontabindex="">Submit</button>
✅ Fixed: valid integer provided
<divtabindex="0">Click me</div>
<buttontabindex="-1">Submit</button>
✅ Fixed: attribute removed when not needed
Interactive elements like <button> are focusable by default, so tabindex can simply be removed:
<button>Submit</button>
✅ Fixed: conditional rendering in a template
If you're using a templating system, ensure the attribute is only rendered when a valid value exists:
<!-- Instead of always outputting tabindex -->
<!-- Bad: <div tabindex="{{ tabindexValue }}"> -->
<!-- Only output the attribute when a value is set -->
<divtabindex="0">Interactive content</div>
A note on positive tabindex values
While positive integers like tabindex="1" or tabindex="3" are technically valid, they force a custom tab order that diverges from the visual and DOM order of the page. This creates confusion for keyboard users and is difficult to maintain as pages evolve. The WAI-ARIA Authoring Practices recommend avoiding positive tabindex values. Stick with 0 and -1 in nearly all cases.
When the W3C HTML Validator reports "Bad value X for attribute src on element iframe: Illegal character in query: [ is not allowed", it means your URL contains unencoded square brackets in the query portion (everything after the ?). While most modern browsers will silently handle these characters and load the resource anyway, the URL does not conform to the URI syntax defined in RFC 3986, which the HTML specification requires.
According to RFC 3986, square brackets are reserved characters that have a specific purpose: they are only permitted in the host component of a URI to denote IPv6 addresses (e.g., http://[::1]/). Anywhere else in the URI — including the query string — they must be percent-encoded. The HTML living standard (WHATWG) requires that URLs in attributes like src, href, and action be valid URLs, which means they must follow these encoding rules.
Why this matters
- Standards compliance: Invalid URLs cause W3C validation failures, which can indicate deeper issues in your markup.
- Interoperability: While mainstream browsers are forgiving, some HTTP clients, proxies, CDNs, or web application firewalls may reject or mangle URLs with unencoded square brackets.
- Consistent behavior: Percent-encoding reserved characters guarantees that the URL is interpreted the same way everywhere — in browsers, server logs, link checkers, and automated tools.
- Copy-paste reliability: When users or tools copy a URL from your HTML source, an already-encoded URL is less likely to break during transmission through email clients, messaging apps, or other systems.
How to fix it
Replace every occurrence of [ with %5B and ] with %5D within the query string of the URL. If you're generating URLs server-side or in JavaScript, use the language's built-in URL encoding functions rather than doing manual find-and-replace:
- JavaScript:
encodeURIComponent()or theURL/URLSearchParamsAPIs - PHP:
urlencode()orhttp_build_query() - Python:
urllib.parse.urlencode()orurllib.parse.quote()
These functions will automatically encode square brackets and any other reserved characters.
Examples
❌ Invalid — unencoded square brackets in query string
<iframesrc="https://example.com/embed?filter[status]=active&filter[type]=video"></iframe>
The validator flags [ and ] as illegal characters in the query component.
✅ Valid — square brackets percent-encoded
<iframesrc="https://example.com/embed?filter%5Bstatus%5D=active&filter%5Btype%5D=video"></iframe>
Replacing [ with %5B and ] with %5D resolves the error. The server receives the exact same parameter values — most server-side frameworks (PHP, Rails, etc.) automatically decode percent-encoded characters before processing them.
❌ Invalid — square brackets in a timestamp parameter
<iframesrc="https://example.com/report?time=[2024-01-01]"></iframe>
✅ Valid — timestamp parameter properly encoded
<iframesrc="https://example.com/report?time=%5B2024-01-01%5D"></iframe>
Generating encoded URLs in JavaScript
If you're setting the src dynamically, let the browser handle encoding for you:
<iframeid="report-frame"></iframe>
<script>
consturl=newURL("https://example.com/embed");
url.searchParams.set("filter[status]","active");
url.searchParams.set("filter[type]","video");
document.getElementById("report-frame").src=url.toString();
</script>
The URLSearchParams API automatically percent-encodes the brackets, producing a valid URL in the src attribute.
A note on other elements
This same rule applies to any HTML attribute that accepts a URL — including href on <a> elements, action on <form> elements, and src on <script> or <img> elements. Whenever you place a URL in HTML, ensure all reserved characters in the query string are properly percent-encoded.
A backslash (\) is not a valid character in a URL fragment and must be replaced with a forward slash (/) or percent-encoded.
The href attribute on an <a> element must contain a valid URL according to the URL Living Standard. The fragment portion of a URL — the part after the # symbol — follows the same rule: it can contain most characters, but the backslash (\) is explicitly forbidden as a bare character.
This commonly happens when copying file paths from Windows, which uses backslashes as directory separators, and pasting them into an href. Browsers may silently convert \ to /, but the markup is still invalid.
To fix this, replace every \ with / in the URL. If for some reason you actually need a literal backslash in the fragment, percent-encode it as %5C.
HTML Examples
❌ Invalid: backslash in the fragment
<ahref="page.html#section\one">Link</a>
✅ Fixed: use a forward slash or percent-encoding
<!-- Option 1: Replace with forward slash -->
<ahref="page.html#section/one">Link</a>
<!-- Option 2: Percent-encode the backslash -->
<ahref="page.html#section%5Cone">Link</a>
The href attribute expects a valid URL, and URLs follow strict syntax rules defined by RFC 3986. Under these rules, spaces are not permitted anywhere in a URL — not in the path, the query string, the fragment, or any other component. When a browser encounters a space in an href, it may attempt to fix the URL by encoding the space automatically, but this behavior is not guaranteed to be consistent across all browsers and contexts. Relying on browsers to silently correct invalid URLs is fragile and can lead to broken links.
This matters for several reasons. First, standards compliance: the W3C validator flags this because the HTML specification requires href values to be valid URLs. Second, interoperability: while most modern browsers handle spaces gracefully on navigation, other consumers of your HTML — such as web crawlers, screen readers, link checkers, and APIs that parse HTML — may not. Third, accessibility: assistive technologies rely on well-formed URLs to correctly announce and follow links. A malformed URL could lead to unexpected behavior for users depending on these tools.
The fix is straightforward: replace every literal space character with %20. This is called percent-encoding (sometimes called URL encoding). The sequence %20 is the hexadecimal representation of the space character (ASCII code 32). In the query string portion of a URL specifically, you may also see + used to represent spaces (as defined by the application/x-www-form-urlencoded format), but %20 is universally valid across all parts of a URL and is the safer choice.
Be aware that spaces can sometimes be hard to spot, especially trailing spaces or spaces introduced by template engines and CMS platforms that concatenate URL parts. If you're generating URLs dynamically (e.g., in a server-side template or JavaScript), use built-in encoding functions like encodeURIComponent() in JavaScript or urlencode() in PHP rather than manually replacing spaces.
Examples
Incorrect: space in the query string
<ahref="search.html?q=my search">Search for 'my search'</a>
The literal space between my and search makes this an invalid URL.
Correct: space replaced with %20
<ahref="search.html?q=my%20search">Search for 'my search'</a>
Incorrect: spaces in the path
<ahref="/files/my document.pdf">Download the document</a>
Spaces in the path segment are equally invalid.
Correct: spaces in the path encoded
<ahref="/files/my%20document.pdf">Download the document</a>
Incorrect: multiple spaces across path and query
<ahref="/product catalog/items?name=red shoes&category=on sale">Red Shoes</a>
Correct: all spaces encoded
<ahref="/product%20catalog/items?name=red%20shoes&category=on%20sale">Red Shoes</a>
Note that in addition to encoding the spaces, the & in the query string should be written as & in HTML to avoid being interpreted as the start of an HTML entity.
Using JavaScript to encode URLs dynamically
If you're building URLs in JavaScript, use encodeURIComponent() for individual parameter values or encodeURI() for full URLs:
<script>
constquery="my search";
consturl="search.html?q="+encodeURIComponent(query);
// Result: "search.html?q=my%20search"
</script>
This approach prevents encoding issues by handling all special characters automatically — not just spaces, but also characters like #, &, =, and others that have special meaning in URLs.
A span element has an implicit ARIA role of generic, and the aria-labelledby attribute is not allowed on elements with that role.
The span element is a generic inline container with no semantic meaning. Its default ARIA role is generic, and the ARIA specification explicitly prohibits naming generic elements with aria-labelledby (or aria-label). This restriction exists because accessible names on generic containers create confusing experiences for assistive technology users — screen readers wouldn't know what kind of thing is being labeled.
To fix this, you have two main options:
- Add a meaningful
roleto thespanthat supportsaria-labelledby, such asrole="group",role="region", or any other role that accepts a label. - Use a more semantic element that already has an appropriate role, like a
section,nav, ordivwith an explicit role.
If the span doesn't truly need a label, simply remove the aria-labelledby attribute.
HTML Examples
❌ Invalid: aria-labelledby on a plain span
<spanid="label">Settings</span>
<spanaria-labelledby="label">
<inputtype="checkbox"id="opt1">
<labelfor="opt1">Enable notifications</label>
</span>
✅ Fix: Add an appropriate role
<spanid="label">Settings</span>
<spanrole="group"aria-labelledby="label">
<inputtype="checkbox"id="opt1">
<labelfor="opt1">Enable notifications</label>
</span>
✅ Fix: Use a semantic element instead
<spanid="label">Settings</span>
<fieldsetaria-labelledby="label">
<inputtype="checkbox"id="opt1">
<labelfor="opt1">Enable notifications</label>
</fieldset>
An aria-label attribute on an <a> element is only valid when the link has an accessible role that supports naming — which means the <a> must have an href attribute or an explicit role that accepts a label.
When an <a> element lacks an href attribute, it has the implicit role of generic. The generic role is in the list of roles that do not support naming, so applying aria-label to it is invalid. This is because a generic element has no semantic meaning, and screen readers wouldn't know how to announce the label in a meaningful way.
The most common cause of this error is using <a> as a placeholder or JavaScript-only trigger without an href. An <a> with an href has the implicit role of link, which does support aria-label, so the error won't appear.
You have a few ways to fix this:
- Add an
hrefto make it a proper link (most common fix). - Add an explicit role that supports naming, such as
role="button", if the element acts as a button. - Use a
<button>instead if the element triggers an action rather than navigation. - Remove
aria-labelif it's not needed, and use visible text content instead.
HTML Examples
❌ Invalid: aria-label on an <a> without href
<aaria-label="Close menu"onclick="closeMenu()">✕</a>
The <a> has no href, so its implicit role is generic, which does not support naming.
✅ Fix option 1: Add an href
<ahref="/close"aria-label="Close menu">✕</a>
✅ Fix option 2: Use a <button> instead
<buttonaria-label="Close menu"onclick="closeMenu()">✕</button>
✅ Fix option 3: Add an explicit role that supports naming
<arole="button"tabindex="0"aria-label="Close menu"onclick="closeMenu()">✕</a>
Using a <button> (option 2) is generally the best choice for interactive elements that perform actions rather than navigate to a URL.
A span element has an implicit ARIA role of generic, and the aria-label attribute is not allowed on elements with that role.
The span element is a generic inline container with no semantic meaning. Its default ARIA role is generic, which is one of several roles that prohibit naming via aria-label or aria-labelledby. This restriction exists because screen readers are not expected to announce names for generic containers — adding aria-label to them creates an inconsistent and confusing experience for assistive technology users.
To fix this, you have two main options:
- Assign an explicit role to the
spanthat supports naming, such asrole="img",role="group",role="status", or any other role that allowsaria-label. - Use a different element that already has a semantic role supporting
aria-label, such as abutton,a,section, ornav.
If the span is purely decorative or used for styling, consider using aria-hidden="true" instead and placing accessible text elsewhere.
HTML Examples
❌ Invalid: aria-label on a plain span
<spanaria-label="Close">✕</span>
✅ Fixed: assign an appropriate role
<spanrole="img"aria-label="Close">✕</span>
✅ Fixed: use a semantic element instead
<buttonaria-label="Close">✕</button>
✅ Fixed: hide the decorative span and provide text another way
<button>
<spanaria-hidden="true">✕</span>
<spanclass="visually-hidden">Close</span>
</button>
A div element without an explicit role resolves to the generic role, which does not support naming — so adding aria-label to a plain div is invalid.
The aria-label attribute provides an accessible name for an element, but not every element is allowed to have one. The ARIA specification defines certain roles as "naming prohibited," meaning assistive technologies will ignore any accessible name applied to them. The generic role is one of these, and since a div without an explicit role attribute defaults to generic, the aria-label is effectively meaningless.
To fix this, you have two main options: assign an appropriate ARIA role to the div so it becomes a nameable landmark or widget, or switch to a semantic HTML element that already carries a valid role. Common roles that support naming include region, group, navigation, alert, and many others.
If the div is truly just a generic wrapper with no semantic meaning, consider whether aria-label is even needed. Perhaps the label belongs on a child element instead, or the content is already self-describing.
HTML Examples
❌ Invalid: aria-label on a plain div
<divaria-label="User profile section">
<p>Welcome, Jane!</p>
</div>
✅ Fix: Add an appropriate role
<divrole="region"aria-label="User profile section">
<p>Welcome, Jane!</p>
</div>
✅ Fix: Use a semantic element instead
<sectionaria-label="User profile section">
<p>Welcome, Jane!</p>
</section>
The <time> element does not support the aria-label attribute when it has no explicit role or when it carries certain generic roles.
The <time> element has an implicit ARIA role of time, but this role is not listed among those that allow aria-label. According to the ARIA in HTML specification, aria-label is only permitted on elements with roles that support naming from author — and the default role of <time> (as well as roles like generic, presentation, paragraph, and others listed in the error) does not qualify.
In practice, the <time> element already conveys its meaning through its visible text content and the machine-readable datetime attribute. Screen readers use the visible text to announce the date, so aria-label is typically unnecessary.
To fix this, simply remove the aria-label attribute and ensure the visible text content is descriptive enough. If you need to provide a more accessible reading of the date, you can adjust the visible text itself or wrap the element with a <span> that has an appropriate role.
Also note the original code has a missing space before datetime — the attribute must be separated from class="Tag" by a space.
HTML Examples
❌ Invalid: aria-label on <time>
<timearia-label="Apr 2."class="Tag"datetime="2026-04-02">
Apr 2.
</time>
✅ Fixed: Remove aria-label and use clear visible text
<timeclass="Tag"datetime="2026-04-02">
April 2, 2026
</time>
If you truly need aria-label, you can assign an explicit role that supports naming, such as role="text", though this is rarely necessary:
<timerole="text"aria-label="April 2nd, 2026"class="Tag"datetime="2026-04-02">
Apr 2.
</time>
A div element without an explicit role (or with role="generic") cannot have the aria-labelledby attribute because generic containers have no semantic meaning that benefits from a label.
The div element maps to the generic ARIA role by default. Generic elements are purely structural — they don't represent anything meaningful to assistive technologies. Labeling something that has no semantic purpose creates a confusing experience for screen reader users, since the label points to an element that doesn't convey a clear role.
The aria-labelledby attribute is designed for interactive or landmark elements — things like dialog, region, navigation, form, or group — where a label helps users understand the purpose of that section.
To fix this, you have two options: assign a meaningful ARIA role to the div, or use a more semantic HTML element that naturally supports labeling.
HTML Examples
❌ Invalid: aria-labelledby on a plain div
<h2id="section-title">User Settings</h2>
<divaria-labelledby="section-title">
<p>Manage your account preferences here.</p>
</div>
✅ Fixed: Add a meaningful role to the div
<h2id="section-title">User Settings</h2>
<divrole="region"aria-labelledby="section-title">
<p>Manage your account preferences here.</p>
</div>
✅ Fixed: Use a semantic element instead
<h2id="section-title">User Settings</h2>
<sectionaria-labelledby="section-title">
<p>Manage your account preferences here.</p>
</section>
Using a section element or adding role="region" tells assistive technologies that this is a distinct, meaningful area of the page — making the label useful and the markup valid.
The aria-label attribute cannot be used on an <i> element with its default implicit role (generic), because generic elements are not allowed to have accessible names.
The <i> element has an implicit ARIA role of generic, which is one of the roles explicitly prohibited from carrying an aria-label. This restriction exists because screen readers and other assistive technologies ignore accessible names on generic containers — so adding aria-label to a plain <i> element would silently fail to convey any meaning to users who rely on assistive technology.
This issue commonly appears when icon fonts (like Font Awesome) use <i> elements as decorative icons. If the icon is purely decorative, you should hide it from assistive technology with aria-hidden="true" and place the accessible label on a parent or sibling element instead. If the icon conveys meaning on its own, you can assign an appropriate role like role="img" so the aria-label is actually announced.
HTML Examples
❌ Invalid: aria-label on a plain <i> element
<button>
<iclass="icon-search"aria-label="Search"></i>
</button>
✅ Fix 1: Decorative icon — hide it, label the parent
<buttonaria-label="Search">
<iclass="icon-search"aria-hidden="true"></i>
</button>
✅ Fix 2: Meaningful icon — assign role="img"
<button>
<iclass="icon-search"role="img"aria-label="Search"></i>
</button>
The rel attribute defines the relationship between the current document and a linked resource. The HTML specification maintains a set of recognized keyword values for this attribute, and the allowed keywords vary depending on which element the attribute appears on. For example, stylesheet is valid on <link> but not on <a>, while nofollow is valid on <a> and <form> but not on <link>.
When the validator encounters a rel value that isn't a recognized keyword, it checks whether the value is a valid absolute URL. This is because the HTML specification allows custom link types to be defined using absolute URLs as identifiers (similar to how XML namespaces work). If the value is neither a recognized keyword nor a valid absolute URL, the validator raises this error.
Common causes of this error include:
- Typos in standard keywords — for example,
rel="styelsheet"orrel="no-follow"instead of the correctrel="stylesheet"orrel="nofollow". - Using non-standard or invented values — such as
rel="custom"orrel="external", which aren't part of the HTML specification's recognized set. - Using relative URLs as custom link types — for example,
rel="my-custom-type"instead of a full URL likerel="https://example.com/my-custom-type".
This matters because browsers and other user agents rely on recognized rel values to determine how to handle linked resources. An unrecognized value will simply be ignored, which could mean your stylesheet doesn't load, your prefetch hint doesn't work, or search engines don't respect your intended link relationship. Using correct values ensures predictable behavior across all browsers and tools.
Examples
Incorrect: Misspelled keyword
<linkrel="styleshet"href="main.css">
The validator reports that styleshet is not a recognized keyword and is not an absolute URL.
Correct: Fixed spelling
<linkrel="stylesheet"href="main.css">
Incorrect: Non-standard keyword on an anchor
<ahref="https://example.com"rel="external">Visit Example</a>
The value external is not a standard rel keyword in the HTML specification, so the validator flags it.
Correct: Using a recognized keyword
<ahref="https://example.com"rel="noopener">Visit Example</a>
Incorrect: Relative URL as a custom link type
<linkrel="my-custom-rel"href="data.json">
Correct: Absolute URL as a custom link type
If you genuinely need a custom relationship type, provide a full absolute URL:
<linkrel="https://example.com/rels/my-custom-rel"href="data.json">
Correct: Common valid rel values
Here are some frequently used standard rel keywords with their appropriate elements:
<!-- Linking a stylesheet -->
<linkrel="stylesheet"href="styles.css">
<!-- Linking a favicon -->
<linkrel="icon"href="favicon.ico">
<!-- Preloading a resource -->
<linkrel="preload"href="font.woff2"as="font"type="font/woff2"crossorigin>
<!-- Telling search engines not to follow a link -->
<ahref="https://example.com"rel="nofollow">Sponsored link</a>
<!-- Opening a link safely in a new tab -->
<ahref="https://example.com"target="_blank"rel="noopener noreferrer">External site</a>
Multiple rel values
You can specify multiple space-separated rel values. Each one must individually be either a recognized keyword or a valid absolute URL:
<!-- Correct: both values are recognized keywords -->
<ahref="https://example.com"target="_blank"rel="noopener noreferrer">External</a>
<!-- Incorrect: "popup" is not a recognized keyword or absolute URL -->
<ahref="https://example.com"target="_blank"rel="noopener popup">External</a>
To resolve this error, consult the MDN rel attribute reference for the full list of recognized keywords and which elements support them. If your value isn't on the list, either replace it with the correct standard keyword or use a complete absolute URL to define your custom link type.
The aria-label attribute cannot be used on a custom element like <menu-item> when it has no explicit role attribute, because it defaults to the generic role, which is in the list of roles that prohibit aria-label.
Custom elements without an explicit role are treated as having the generic role (equivalent to a <span> or <div> in terms of semantics). The WAI-ARIA specification prohibits aria-label on several roles, including generic, because naming these elements creates a confusing experience for assistive technology users — a generic container with a label doesn't convey any meaningful purpose.
To fix this, you need to assign a meaningful role to the <menu-item> element that supports accessible naming. Common choices include role="menuitem", role="link", or role="button", depending on what the element actually does. Since this appears to represent a menu item that navigates to a page, role="menuitem" is likely the most appropriate.
HTML Examples
❌ Invalid: aria-label on an element with implicit generic role
<menu-item
submenu-href="/page"
label="some label"
submenu-title="some submenu title"
aria-label="some aria label">
</menu-item>
✅ Valid: adding an explicit role that supports aria-label
<menu-item
role="menuitem"
submenu-href="/page"
label="some label"
submenu-title="some submenu title"
aria-label="some aria label">
</menu-item>
If the aria-label isn't actually needed (for example, if assistive technology already receives the label through other means in your component), another valid fix is to simply remove aria-label entirely.
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