HTML Guides
Learn how to identify and fix common HTML validation errors flagged by the W3C Validator — so your pages are standards-compliant and render correctly across every browser. Also check our Accessibility Guides.
The 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 space character in the fragment portion of a URL (the part after #) is not allowed in the src attribute of an img element.
URLs follow the syntax defined in RFC 3986, which does not permit literal spaces anywhere in the URI, including the fragment identifier. Browsers may silently handle spaces by percent-encoding them, but the raw HTML remains invalid. The W3C validator flags this because the src value must be a valid URL before the browser ever processes it.
To fix the issue, replace each space in the URL with %20, which is the percent-encoded form of a space character. This applies to all parts of the URL: the path, query string, and fragment.
A common cause is copying a URL from a browser address bar or a document system where filenames or anchors contain spaces. Another cause is linking to a named anchor (id) on a page where the id value itself contains a space, which is also invalid HTML — id values must not contain spaces.
HTML examples
Invalid: space in fragment
<imgsrc="diagram.svg#my section"alt="Diagram of section layout">
Valid: percent-encoded space
<imgsrc="diagram.svg#my%20section"alt="Diagram of section layout">
If you control the target resource, a better fix is to remove the space from the fragment identifier entirely:
<imgsrc="diagram.svg#my-section"alt="Diagram of section layout">
Using hyphens or underscores instead of spaces in fragment identifiers and file names avoids this class of problem altogether.
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>
A space character in the href attribute of an <a> element is not valid in a URL. Spaces must be encoded as %20 in the query string.
URLs follow the syntax defined in RFC 3986, which does not allow literal space characters anywhere in the URL. When the W3C validator encounters a space in a query string, it flags the href value as malformed. Browsers often handle this gracefully by encoding the space automatically, but the HTML itself is still invalid.
The query component of a URL (everything after the ?) follows the same rule. If a value in the query string contains a space, replace each space with %20. An alternative encoding, +, is specific to the application/x-www-form-urlencoded format used by HTML form submissions, and is also accepted in practice by most servers. However, %20 is the universally correct percent-encoding for a space in any part of a URL.
Invalid example
<ahref="https://example.com/search?query=hello world&lang=en">Search</a>
The space between hello and world causes the validation error.
Valid example
<ahref="https://example.com/search?query=hello%20world&lang=en">Search</a>
Every space in the URL is replaced with %20, and the markup passes validation.
A mailto: link in an href attribute contains a [ character that is not valid in a URI according to RFC 3986.
Square brackets ([ and ]) are reserved characters in URIs and must be percent-encoded when used outside their specific purpose (IPv6 address literals). If the email address or other part of the mailto: URI contains a [, it must be encoded as %5B. Similarly, ] must be encoded as %5D.
This error typically appears when a mailto: link includes square brackets in the subject, body, or display-related parts of the URL. Query parameters in mailto: links — such as ?subject= or ?body= — must have their values properly percent-encoded.
Invalid example
<ahref="mailto:info@example.com?subject=[Action Required] Review">
Email us
</a>
Valid example
Percent-encode the square brackets in the query string:
<ahref="mailto:info@example.com?subject=%5BAction%20Required%5D%20Review">
Email us
</a>
The [ becomes %5B, the ] becomes %5D, and spaces become %20. Most server side languages and JavaScript provide functions to handle this encoding automatically, such as encodeURIComponent() in JavaScript.
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.
The WAI-ARIA specification defines a strict ownership model for tab-related roles. An element with role="tab" controls the visibility of an associated role="tabpanel" element, and tabs are expected to be grouped within a tablist. This relationship is how assistive technologies like screen readers understand and communicate the tab interface pattern to users — for example, announcing "tab 2 of 4" when focus moves between tabs.
When a tab is not contained in or owned by a tablist, screen readers cannot determine how many tabs exist in the group, which tab is currently selected, or how to navigate between them. This fundamentally breaks the accessibility of the tab interface, making it confusing or unusable for people who rely on assistive technologies.
There are two ways to establish the required relationship:
- Direct containment: Place the
role="tab"elements as direct children of therole="tablist"element. This is the most common and straightforward approach. - Using
aria-owns: If the DOM structure prevents direct nesting, addaria-ownsto thetablistelement with a space-separated list ofidvalues referencing each tab. This tells assistive technologies that thetablistowns those tabs even though they aren't direct children in the DOM.
Examples
Incorrect: tab outside of a tablist
In this example, the role="tab" buttons are siblings of the tablist rather than children of it, which triggers the validation error.
<divclass="tabs">
<divrole="tablist"aria-label="Sample Tabs"></div>
<buttonrole="tab"aria-selected="true"aria-controls="panel-1"id="tab-1">
First Tab
</button>
<buttonrole="tab"aria-selected="false"aria-controls="panel-2"id="tab-2">
Second Tab
</button>
</div>
Correct: tabs as direct children of tablist
The simplest fix is to place the role="tab" elements directly inside the role="tablist" element.
<divclass="tabs">
<divrole="tablist"aria-label="Sample Tabs">
<buttonrole="tab"aria-selected="true"aria-controls="panel-1"id="tab-1"tabindex="0">
First Tab
</button>
<buttonrole="tab"aria-selected="false"aria-controls="panel-2"id="tab-2"tabindex="-1">
Second Tab
</button>
</div>
<divid="panel-1"role="tabpanel"tabindex="0"aria-labelledby="tab-1">
<p>Content for the first panel</p>
</div>
<divid="panel-2"role="tabpanel"tabindex="0"aria-labelledby="tab-2"hidden>
<p>Content for the second panel</p>
</div>
</div>
Correct: using aria-owns when DOM nesting isn't possible
If your layout or framework makes it difficult to nest the tabs directly inside the tablist, you can use aria-owns to establish the relationship programmatically.
<divclass="tabs">
<divrole="tablist"aria-label="Sample Tabs"aria-owns="tab-1 tab-2"></div>
<divclass="tab-wrapper">
<buttonrole="tab"aria-selected="true"aria-controls="panel-1"id="tab-1"tabindex="0">
First Tab
</button>
<buttonrole="tab"aria-selected="false"aria-controls="panel-2"id="tab-2"tabindex="-1">
Second Tab
</button>
</div>
<divid="panel-1"role="tabpanel"tabindex="0"aria-labelledby="tab-1">
<p>Content for the first panel</p>
</div>
<divid="panel-2"role="tabpanel"tabindex="0"aria-labelledby="tab-2"hidden>
<p>Content for the second panel</p>
</div>
</div>
Additional notes
- Each
role="tab"element should usearia-selectedto indicate which tab is active ("true") and which are not ("false"). - Use
aria-controlson each tab to reference theidof its associatedtabpanel. - Use
aria-labelledbyon eachtabpanelto point back to its controlling tab. - Set
tabindex="0"on the active tab andtabindex="-1"on inactive tabs to support keyboard navigation with arrow keys within thetablist. - Always include an
aria-labeloraria-labelledbyon thetablistto give it an accessible name.
The preload value of the <link> element's rel attribute lets you declare fetch requests in the HTML <head>, telling the browser to start loading critical resources early in the page lifecycle—before the main rendering machinery kicks in. This can significantly improve performance by ensuring key assets are available sooner and are less likely to block rendering.
However, a preload hint is incomplete without the as attribute. The as attribute tells the browser what kind of resource is being fetched. This matters for several important reasons:
- Request prioritization: Browsers assign different priorities to different resource types. A stylesheet is typically higher priority than an image. Without
as, the browser cannot apply the correct priority, and the preloaded resource may be fetched with a low default priority, undermining the purpose of preloading. - Content Security Policy (CSP): CSP rules are applied based on resource type (e.g.,
script-src,style-src). Without knowing the type, the browser cannot enforce the appropriate policy. - Correct
Acceptheader: Theasvalue determines whichAcceptheader the browser sends with the request. For example, an image request sends a differentAcceptheader than a script request. An incorrect or missing header could lead to unexpected responses from the server. - Cache matching: When the resource is later requested by a
<script>,<link rel="stylesheet">, or other element, the browser needs to match it against the preloaded resource in its cache. Withoutas, the browser may not recognize the preloaded resource and could fetch it again, resulting in a duplicate request—the opposite of what you intended.
The HTML specification explicitly requires the as attribute when rel="preload" is used, making this a conformance error.
Common as Values
The as attribute accepts a specific set of values. Here are the most commonly used ones:
| Value | Resource Type |
|---|---|
script | JavaScript files |
style | CSS stylesheets |
font | Web fonts |
image | Images |
fetch | Resources fetched via fetch() or XMLHttpRequest |
document | HTML documents (for <iframe>) |
audio | Audio files |
video | Video files |
track | WebVTT subtitle tracks |
worker | Web workers or shared workers |
Examples
Incorrect: Missing as attribute
This will trigger the validation error because the browser doesn't know what type of resource is being preloaded:
<linkrel="preload"href="/fonts/roboto.woff2">
<linkrel="preload"href="/js/app.js">
Correct: as attribute included
Adding the appropriate as value tells the browser exactly what kind of resource to expect:
<linkrel="preload"href="/fonts/roboto.woff2"as="font"type="font/woff2"crossorigin>
<linkrel="preload"href="/js/app.js"as="script">
<linkrel="preload"href="/css/main.css"as="style">
<linkrel="preload"href="/images/hero.webp"as="image">
Note on fonts and crossorigin
When preloading fonts, you must also include the crossorigin attribute, even if the font is hosted on the same origin. This is because font fetches are CORS requests by default. Without crossorigin, the preloaded font won't match the later font request and will be fetched twice:
<!-- Correct: includes both as and crossorigin for fonts -->
<linkrel="preload"href="/fonts/roboto.woff2"as="font"type="font/woff2"crossorigin>
Full document example
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>Preload Example</title>
<linkrel="preload"href="/css/main.css"as="style">
<linkrel="preload"href="/js/app.js"as="script">
<linkrel="preload"href="/fonts/roboto.woff2"as="font"type="font/woff2"crossorigin>
<linkrel="stylesheet"href="/css/main.css">
</head>
<body>
<h1>Hello, world!</h1>
<scriptsrc="/js/app.js"></script>
</body>
</html>
Choosing the correct as value is straightforward—just match it to how the resource will ultimately be used on the page. If you're preloading a stylesheet, use as="style"; if it's a script, use as="script", and so on.
The ARIA specification defines a strict ownership hierarchy for table-related roles. A columnheader represents a header cell for a column, analogous to a <th> element in native HTML. For assistive technologies like screen readers to correctly announce column headers and associate them with the data cells beneath them, the columnheader must exist within a row context. The required structure is:
- An element with
role="table",role="grid", orrole="treegrid"serves as the container. - Inside it, elements with
role="rowgroup"(optional) orrole="row"organize the rows. - Each
role="row"element contains one or more elements withrole="columnheader",role="rowheader", orrole="cell".
When a role="columnheader" element is placed directly inside a role="table" or role="grid" container — or any other element that is not role="row" — the validator raises this error. Without the row wrapper, screen readers cannot navigate the table structure properly. Users who rely on assistive technology may hear disjointed content or miss the column headers entirely, making the data table unusable.
The best practice, whenever feasible, is to use native HTML table elements (<table>, <thead>, <tr>, <th>, <td>). These carry implicit ARIA roles and establish the correct ownership relationships automatically, eliminating this entire category of errors. Only use ARIA table roles when you genuinely cannot use native table markup — for example, when building a custom grid widget with non-table elements for layout reasons.
Examples
Incorrect: columnheader not inside a row
In this example, the columnheader elements are direct children of the table container, with no role="row" wrapper:
<divrole="table"aria-label="Employees">
<divrole="columnheader">Name</div>
<divrole="columnheader">Department</div>
<divrole="row">
<divrole="cell">Alice</div>
<divrole="cell">Engineering</div>
</div>
</div>
Correct: columnheader inside a row
Wrapping the column headers in an element with role="row" fixes the issue:
<divrole="table"aria-label="Employees">
<divrole="row">
<divrole="columnheader">Name</div>
<divrole="columnheader">Department</div>
</div>
<divrole="row">
<divrole="cell">Alice</div>
<divrole="cell">Engineering</div>
</div>
</div>
Correct: Using rowgroup for additional structure
You can optionally use role="rowgroup" to separate headers from body rows, similar to <thead> and <tbody>. The columnheader elements must still be inside a row:
<divrole="table"aria-label="Employees">
<divrole="rowgroup">
<divrole="row">
<divrole="columnheader">Name</div>
<divrole="columnheader">Department</div>
</div>
</div>
<divrole="rowgroup">
<divrole="row">
<divrole="cell">Alice</div>
<divrole="cell">Engineering</div>
</div>
</div>
</div>
Best practice: Use native table elements
Native HTML tables have built-in semantics that make ARIA roles unnecessary. A <th> inside a <tr> already behaves as a columnheader inside a row:
<table>
<thead>
<tr>
<th>Name</th>
<th>Department</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alice</td>
<td>Engineering</td>
</tr>
</tbody>
</table>
This approach is simpler, more robust across browsers and assistive technologies, and avoids the risk of ARIA misuse. Reserve ARIA table roles for situations where native table markup is not an option.
In URLs, special characters that aren't part of the standard allowed set must be represented using percent-encoding (also called URL encoding). This works by replacing the character with a % followed by two hexadecimal digits representing its byte value — for example, a space becomes %20, and an ampersand becomes %26. Because % itself serves as the escape character in this scheme, any bare % that isn't followed by two hexadecimal digits creates an ambiguous, invalid URL.
When the W3C validator encounters a src attribute containing a % not followed by two valid hex digits, it cannot determine the intended character and reports the error. This issue typically arises in two scenarios:
- A literal percent sign in the URL — for instance, a query parameter like
?width=48%where the%is meant as an actual percent symbol. The%must be encoded as%25. - Incomplete or corrupted percent-encoding — such as
%2instead of%20, or%GZwhere the characters after%aren't valid hexadecimal digits (only0–9andA–F/a–fare valid).
Why this matters
- Browser inconsistency: While many browsers try to handle malformed URLs gracefully, behavior varies. Some browsers may misinterpret the intended resource path, leading to broken images or unexpected requests.
- Standards compliance: The URL Living Standard and RFC 3986 define strict rules for percent-encoding. Invalid URLs violate these standards.
- Reliability: Proxies, CDNs, and server-side software may reject or misroute requests with malformed URLs, causing images to fail to load in certain environments even if they work in your local browser.
How to fix it
- Find every bare
%in the URL that isn't followed by two hexadecimal digits. - If the
%is meant as a literal percent sign, replace it with%25. - If the
%is part of a broken encoding sequence (e.g.,%2or%GH), correct it to the intended two-digit hex code (e.g.,%20for a space). - Use proper URL encoding functions in your language or framework (e.g.,
encodeURIComponent()in JavaScript,urlencode()in PHP) when building URLs dynamically, rather than constructing them by hand.
Examples
Literal percent sign not encoded
<!-- ❌ Bad: bare "%" is not followed by two hex digits -->
<imgalt="Chart"src="https://example.com/chart.png?scale=50%">
<!-- ✅ Fixed: "%" encoded as "%25" -->
<imgalt="Chart"src="https://example.com/chart.png?scale=50%25">
Incomplete percent-encoding sequence
<!-- ❌ Bad: "%2" is incomplete — missing the second hex digit -->
<imgalt="Photo"src="https://example.com/my%2photo.jpg">
<!-- ✅ Fixed: use "%20" for a space character -->
<imgalt="Photo"src="https://example.com/my%20photo.jpg">
Non-hexadecimal characters after percent sign
<!-- ❌ Bad: "%zz" uses non-hex characters -->
<imgalt="Logo"src="https://example.com/logo%zz.png">
<!-- ✅ Fixed: remove the erroneous sequence if it was unintentional -->
<imgalt="Logo"src="https://example.com/logo.png">
Multiple encoding issues in one URL
<!-- ❌ Bad: bare "%" in query value and unencoded space -->
<imgalt="Report"src="https://example.com/img?label=100%&name=my file.png">
<!-- ✅ Fixed: "%" → "%25", space → "%20" -->
<imgalt="Report"src="https://example.com/img?label=100%25&name=my%20file.png">
Building URLs safely with JavaScript
When generating src values in code, use encodeURIComponent() to handle special characters automatically:
constlabel="100%";
constname="my file.png";
constsrc=`https://example.com/img?label=${encodeURIComponent(label)}&name=${encodeURIComponent(name)}`;
// Result: "https://example.com/img?label=100%25&name=my%20file.png"
This ensures every special character — including % — is correctly percent-encoded without manual effort.
The fragment portion of a URL (everything after the # symbol) follows the same encoding rules as the rest of the URL — literal space characters are not permitted. When a browser encounters a space in a URL fragment, it may try to correct it automatically, but this behavior is not guaranteed across all browsers and contexts. Relying on browser error-correction leads to fragile links that may break unpredictably.
This issue matters for several reasons. First, it produces invalid HTML that fails W3C validation. Second, fragment navigation (jumping to a specific section of a page) may not work correctly if the browser doesn't auto-correct the space. Third, assistive technologies like screen readers rely on well-formed URLs to announce link destinations accurately. Finally, tools that parse or process HTML programmatically — such as crawlers, link checkers, and content management systems — may misinterpret or reject malformed URLs.
The most common scenario for this error is linking to an id attribute on the same page or another page where the id contains spaces. However, it's worth noting that id values themselves cannot contain spaces in valid HTML (a space in an id would make it multiple invalid tokens). So if you're writing both the link and the target, the best fix is often to correct the id itself by using hyphens or underscores instead of spaces.
How to Fix It
There are two main approaches:
- Percent-encode the spaces — Replace each space with
%20in thehrefvalue. - Use space-free identifiers — Change the target
idto use hyphens or camelCase, then update the fragment to match.
The second approach is strongly preferred because it fixes the root problem and produces cleaner, more readable markup.
Examples
❌ Invalid: Space in the fragment
<ahref="https://example.com/page#some term">Go to section</a>
<ahref="#my section">Jump to My Section</a>
✅ Fixed: Percent-encode the space
If you cannot control the target id (e.g., linking to an external page), percent-encode the space:
<ahref="https://example.com/page#some%20term">Go to section</a>
✅ Better fix: Use a hyphenated id and matching fragment
When you control both the link and the target, use a space-free id:
<h2id="my-section">My Section</h2>
<p>Some content here.</p>
<ahref="#my-section">Jump to My Section</a>
❌ Invalid: Space in fragment linking to another page
<ahref="/docs/getting-started#quick start guide">Quick Start Guide</a>
✅ Fixed: Matching hyphenated id
<!-- On the target page (/docs/getting-started): -->
<h2id="quick-start-guide">Quick Start Guide</h2>
<!-- On the linking page: -->
<ahref="/docs/getting-started#quick-start-guide">Quick Start Guide</a>
A note on id naming conventions
Since fragment identifiers reference id attributes, adopting a consistent id naming convention avoids this issue entirely. Common patterns include:
<!-- Hyphens (most common, used by many static site generators) -->
<sectionid="getting-started">
<!-- camelCase -->
<sectionid="gettingStarted">
<!-- Underscores -->
<sectionid="getting_started">
All three are valid and will never trigger a space-related validation error in your fragment links.
A < character inside an href attribute is not valid in a URL and must be removed or percent-encoded.
The mailto: URL scheme expects a well formed email address directly after the colon, such as mailto:user@example.com. The angle brackets (< and >) sometimes seen around email addresses in mail headers or plain text are not part of the URL syntax. Including them in the href value produces an illegal character error because < and > are not permitted in URIs without percent-encoding.
If angle brackets appear in your markup, they likely got there by copying an address from an email header like From: User <user@example.com> and pasting the whole thing into the link. Strip the brackets so only the bare address remains.
HTML examples
Incorrect
<ahref="mailto:<user@example.com>">Email us</a>
Correct
<ahref="mailto:user@example.com">Email us</a>
A URL in an href attribute contains a character that must be percent-encoded before it can appear in a query string.
URLs follow the syntax rules defined in RFC 3986. The query component of a URL (everything after the ?) allows a specific set of characters: unreserved characters (A-Z, a-z, 0-9, -, ., _, ~), sub-delimiters (!, $, &, ', (, ), *, +, ,, ;, =), plus :, @, /, and ?. Characters outside this set, such as {, }, |, ^, [, ], spaces, and non-ASCII characters like é or ñ, are illegal in their raw form and must be percent-encoded.
Percent-encoding replaces each disallowed byte with a % followed by its two-digit hexadecimal value. For example, a space becomes %20, a pipe | becomes %7C, and { becomes %7B.
Common causes of this error include:
- Pasting a URL directly from a browser address bar or CMS that displays decoded characters.
- Using curly braces for template placeholders in URLs without encoding them.
- Including spaces or non-ASCII characters in query parameter values.
HTML examples
Invalid: raw illegal characters in the query string
<ahref="https://example.com/search?q=hello world&cat=sci|tech">Search</a>
The space between hello and world and the pipe | between sci and tech are not allowed in a URL query.
Valid: percent-encoded characters
<ahref="https://example.com/search?q=hello%20world&cat=sci%7Ctech">Search</a>
Each illegal character is replaced with its percent-encoded equivalent: space is %20 and | is %7C.
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