HTML Guides for bad value
Learn how to identify and fix common HTML validation errors flagged by the W3C Validator — so your pages are standards-compliant and render correctly across every browser. Also check our Accessibility Guides.
A 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
<link rel="stylesheet" href="https://example.com/styles?family=Open Sans">
Fixed Example
Replace every space with %20:
<link rel="stylesheet" href="https://example.com/styles?family=Open%20Sans">
If your URL has multiple spaces or special characters, make sure each one is properly percent-encoded. Common replacements include %20 for spaces, %26 for & inside already-encoded contexts, and %3D for =. Most programming languages offer a URL-encoding function (e.g., encodeURI() in JavaScript) to handle this automatically.
A link element's href attribute contains a character that is not valid in a URL, such as a space, curly brace, or other unencoded special character.
URLs in HTML must conform to the URL Living Standard. Certain characters are not allowed to appear literally in a URL and must be percent-encoded. Common offenders include spaces (use %20), curly braces { } (use %7B %7D), pipe | (use %7C), and angle brackets < > (use %3C %3E).
This error often appears when a template placeholder like {{variable}} is left unresolved in the href value, or when a URL is copied from another context and contains unencoded characters. The validator reads the raw HTML source, so even if a browser might handle a malformed URL gracefully, the markup is still invalid.
To fix it, replace every illegal character with its percent-encoded equivalent, or remove the character if it does not belong in the URL.
Examples
Invalid: unencoded characters in href
<link rel="stylesheet" href="https://example.com/styles/main file.css">
The space between main and file is not allowed in a URL.
Valid: percent-encoded URL
<link rel="stylesheet" href="https://example.com/styles/main%20file.css">
If the illegal characters come from a template placeholder that was never processed, remove the placeholder or ensure it resolves before the HTML is served:
Invalid: unresolved template syntax
<link rel="icon" href="https://example.com/{{icon_path}}">
Valid: resolved or corrected URL
<link rel="icon" href="https://example.com/images/favicon.ico">
The imagesizes attribute is used exclusively on <link> elements that have rel="preload" and as="image". It works in tandem with the imagesrcset attribute to allow the browser to preload the most appropriate image from a set of candidates — mirroring how sizes and srcset work on an <img> element. When the browser encounters these attributes on a <link>, it can begin fetching the right image resource early, before it even parses the <img> tag in the document body.
When imagesizes is set to an empty string (""), the browser has no information about the intended display size of the image, which defeats the purpose of responsive image preloading. The browser cannot select the best candidate from imagesrcset without knowing how large the image will be rendered. An empty value is invalid per the HTML specification, which requires the attribute to contain a valid source size list (the same syntax used by the sizes attribute on <img>).
This matters for both performance and standards compliance. Responsive preloading is a performance optimization — an empty imagesizes undermines that by leaving the browser unable to make an informed choice. From a standards perspective, the validator correctly rejects the empty value because the attribute's defined value space does not include the empty string.
How to fix it
- Provide a valid sizes value that matches the
sizesattribute on the corresponding<img>element in your page. This tells the browser how wide the image will be at various viewport widths. - Remove
imagesizesentirely if you don't need responsive preloading. If you're preloading a single image (usinghrefinstead ofimagesrcset), you don't needimagesizesat all.
Examples
❌ Bad: empty imagesizes attribute
<link
rel="preload"
as="image"
imagesrcset="hero-480.jpg 480w, hero-800.jpg 800w, hero-1200.jpg 1200w"
imagesizes="">
The empty imagesizes="" triggers the validation error and prevents the browser from selecting the correct image candidate.
✅ Fixed: providing a valid sizes value
<link
rel="preload"
as="image"
imagesrcset="hero-480.jpg 480w, hero-800.jpg 800w, hero-1200.jpg 1200w"
imagesizes="(max-width: 600px) 480px, (max-width: 1000px) 800px, 1200px">
The imagesizes value uses the same syntax as the sizes attribute on <img>. It provides media conditions paired with lengths, with a fallback length at the end. This value should match the sizes attribute on the corresponding <img> element in your markup.
✅ Fixed: simple full-width image
<link
rel="preload"
as="image"
imagesrcset="banner-640.jpg 640w, banner-1280.jpg 1280w"
imagesizes="100vw">
If the image spans the full viewport width, 100vw is a straightforward and valid value.
✅ Fixed: removing the attribute when not needed
<link rel="preload" as="image" href="logo.png">
If you're preloading a single, non-responsive image, omit both imagesrcset and imagesizes and use the href attribute instead. The imagesizes attribute is only meaningful when paired with imagesrcset.
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
<link rel="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
<link rel="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"
{% if responsive_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.
The maxlength attribute controls the maximum number of characters a user can type into a <textarea>. According to the HTML specification, its value must be a valid non-negative integer — that is, a string of one or more ASCII digits like 0, 100, or 5000. An empty string (""), whitespace, negative numbers, or non-numeric values are all invalid. When the browser encounters an invalid maxlength value, its behavior becomes unpredictable — some browsers may ignore the attribute, while others may silently enforce no limit, leading to inconsistent form behavior across platforms.
This issue frequently arises when a server-side template or JavaScript framework conditionally outputs the maxlength attribute but produces an empty value when no limit is configured. For example, a template like maxlength="{{ maxChars }}" will render maxlength="" if the maxChars variable is empty or undefined. The fix is to ensure the attribute is omitted entirely when no value is available, rather than rendering it with an empty string.
Omitting maxlength allows unlimited input. Setting it to 0 is technically valid but prevents the user from entering any characters at all, which is rarely useful. Choose a value that makes sense for your use case, such as the corresponding database column's character limit.
Why this matters
- Standards compliance: The HTML specification explicitly requires a valid non-negative integer. An empty string violates this rule and produces a validation error.
- Consistent behavior: Browsers handle invalid attribute values differently. A valid value ensures the character limit works reliably across all browsers.
- Accessibility: Screen readers and assistive technologies may announce the maximum character limit to users. An empty or invalid value could cause confusing announcements or be silently ignored.
- Form reliability: If your application depends on
maxlengthfor client-side input restrictions (e.g., to match a database column limit), an invalid value means the constraint isn't enforced, potentially leading to data truncation or server errors.
How to fix it
- Set a specific integer value if you need a character limit:
maxlength="200". - Remove the attribute entirely if no limit is needed. An absent
maxlengthmeans unlimited input. - Fix your templates — if you're using a server-side language or JavaScript framework, conditionally render the attribute so it's omitted when no value is provided rather than output as empty.
Examples
❌ Invalid: empty maxlength value
The empty string is not a valid non-negative integer, so this triggers the validation error.
<label for="msg">Message</label>
<textarea id="msg" name="message" maxlength=""></textarea>
❌ Invalid: non-numeric maxlength value
Strings, decimals, and negative numbers are also invalid.
<label for="bio">Bio</label>
<textarea id="bio" name="bio" maxlength="none"></textarea>
<label for="notes">Notes</label>
<textarea id="notes" name="notes" maxlength="-1"></textarea>
✅ Fixed: specific integer value
Set maxlength to the desired character limit.
<label for="msg">Message (max 200 characters)</label>
<textarea id="msg" name="message" maxlength="200"></textarea>
✅ Fixed: attribute omitted entirely
If no character limit is needed, simply remove the attribute.
<label for="msg">Message</label>
<textarea id="msg" name="message"></textarea>
✅ Fixed: conditional rendering in a template
If you're using a templating engine, conditionally include the attribute only when a value exists. The exact syntax depends on your framework — here's a conceptual example:
<!-- Instead of always outputting the attribute: -->
<!-- <textarea maxlength="{{ maxChars }}"></textarea> -->
<!-- Only render it when maxChars has a value: -->
<!-- {% if maxChars %}<textarea maxlength="{{ maxChars }}"></textarea>{% endif %} -->
<label for="feedback">Feedback</label>
<textarea id="feedback" name="feedback" maxlength="500"></textarea>
The name attribute on <a> elements was historically used to create named anchors — fragment targets that could be linked to with href="#anchorName". In modern HTML (the WHATWG living standard), the name attribute on <a> is considered obsolete for this purpose. The id attribute is now the standard way to create fragment targets, and it can be placed on any element, not just <a> tags.
Regardless of whether you use name or id, the value must be a non-empty string. The W3C validator enforces this rule because an empty identifier serves no functional purpose — it cannot be referenced by a fragment link, it cannot be targeted by JavaScript, and it creates invalid markup. Browsers may silently ignore it, but it pollutes the DOM and signals a likely mistake in the code.
Empty name attributes often appear in content migrated from older CMS platforms or WYSIWYG editors that inserted placeholder anchors like <a name=""></a>. They can also result from templating systems where a variable intended to populate the attribute resolved to an empty string.
Why this matters
- Standards compliance: Both the WHATWG HTML living standard and the W3C HTML specification require that identifier-like attributes (
id,name) must not be empty strings. - Accessibility: Screen readers and assistive technologies may attempt to process named anchors. Empty identifiers create noise without providing any navigational value.
- Functionality: An empty
nameoridcannot be used as a fragment target, so the element is effectively useless as a link destination.
How to fix it
- Remove the element entirely if the empty anchor serves no purpose — this is the most common fix.
- Replace
namewithidand provide a meaningful, non-empty value if you need a fragment target. - Move the
idto a nearby semantic element instead of using a standalone empty<a>tag. For example, place theiddirectly on a heading, section, or paragraph. - Ensure uniqueness — every
idvalue in a document must be unique.
Examples
❌ Empty name attribute triggers the error
<a name=""></a>
<h2>Introduction</h2>
<p>Welcome to the guide.</p>
❌ Empty name generated by a template
<a name=""></a>
<p>This anchor was meant to be a target but the value is missing.</p>
<a href="#">Jump to section</a>
✅ Remove the empty anchor if it's unnecessary
<h2>Introduction</h2>
<p>Welcome to the guide.</p>
✅ Use id on the target element directly
<h2 id="introduction">Introduction</h2>
<p>Welcome to the guide.</p>
<!-- Link to the section from elsewhere -->
<a href="#introduction">Go to Introduction</a>
✅ Use id on a standalone anchor if needed
If you need a precise anchor point that doesn't correspond to an existing element, use an <a> tag with a valid, non-empty id:
<a id="section-start"></a>
<p>This paragraph follows the anchor point.</p>
<a href="#section-start">Jump to section start</a>
✅ Migrate legacy name to id
If your existing code uses the obsolete name attribute with a valid value, update it to use id instead:
<!-- Before (obsolete but was valid in HTML4) -->
<a name="contact"></a>
<!-- After (modern HTML) -->
<a id="contact"></a>
<!-- Even better: put the id on a semantic element -->
<h2 id="contact">Contact Us</h2>
The name attribute on an <input> element identifies the form control's data when the form is submitted. It acts as the key in the key-value pair sent to the server (e.g., email=user@example.com). When you set name="", the attribute is present but contains an empty string, which the HTML specification considers an invalid value. An empty name means the input's data will be excluded from the form's submission payload in most browsers, making it functionally useless for form processing.
This issue matters for several reasons:
- Form functionality: Inputs with empty names are typically omitted from form data, so the server never receives the user's input.
- Standards compliance: The HTML specification requires that if the
nameattribute is present, its value must not be empty. - JavaScript references: An empty
namemakes it difficult to reference the element using methods likedocument.getElementsByName()orFormData. - Accessibility: Screen readers and assistive technologies may use the
nameattribute to help identify form controls, and an empty value provides no useful information.
Note that the name attribute is not technically required on every <input> element — it's perfectly valid to omit it entirely. For example, inputs used purely for client-side JavaScript interactions without form submission don't need a name. The error specifically arises when the attribute is present but set to an empty string.
To fix the issue, either assign a meaningful name that describes the data the input collects, or remove the name attribute altogether if the input isn't part of a form submission.
Examples
❌ Empty name attribute triggers the error
<form action="/submit" method="post">
<label for="email">Email:</label>
<input type="email" id="email" name="">
<button type="submit">Submit</button>
</form>
✅ Providing a meaningful name value
<form action="/submit" method="post">
<label for="email">Email:</label>
<input type="email" id="email" name="email">
<button type="submit">Submit</button>
</form>
✅ Removing name when the input isn't submitted
If the input is only used for client-side interaction and doesn't need to be part of form data, simply omit the attribute:
<label for="search">Filter results:</label>
<input type="text" id="search">
❌ Multiple inputs with empty names
This pattern sometimes appears when inputs are generated dynamically with placeholder attributes:
<form action="/register" method="post">
<input type="text" name="">
<input type="password" name="">
<button type="submit">Register</button>
</form>
✅ Each input gets a descriptive name
<form action="/register" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="username">
<label for="password">Password:</label>
<input type="password" id="password" name="password">
<button type="submit">Register</button>
</form>
An empty string is not a valid value for the role attribute. Either assign a recognized WAI-ARIA role or remove the attribute entirely.
The role attribute tells browsers and assistive technologies what purpose an element serves in the page. Every value must be a valid ARIA role token defined in the WAI-ARIA specification, such as banner, navigation, alert, dialog, or presentation. When set to an empty string (""), the attribute has no meaningful value, and the W3C validator rejects it.
Some templating engines and JavaScript frameworks conditionally set the role attribute but output an empty string when no role applies. In those cases, the fix is to either omit the attribute altogether or conditionally render it only when a valid role is available.
If the element does not need an explicit ARIA role, remove the attribute. Native HTML elements like <nav>, <header>, <main>, and <aside> already carry implicit roles, so adding role to them is usually unnecessary.
HTML examples
Invalid: empty role attribute
<div role="">
<p>Some content</p>
</div>
Fixed: remove the attribute or assign a valid role
Remove the attribute when no specific role is needed:
<div>
<p>Some content</p>
</div>
Or assign a valid ARIA role:
<div role="alert">
<p>Something went wrong.</p>
</div>
Square brackets ([ and ]) are reserved characters under RFC 3986, the standard that defines URI syntax. They are only permitted in the host component of a URL (specifically for IPv6 addresses) and are illegal elsewhere, including the query string. While most browsers are lenient and will load an <iframe> even when the src contains raw brackets, the W3C HTML Validator correctly flags this as an invalid URL value.
This pattern surfaces frequently when working with frameworks or APIs that use bracket notation to represent arrays or nested objects in query parameters — for example, filters[category]=news or items[0]=apple. These URLs work in a browser's address bar because browsers silently fix malformed URLs, but the raw HTML itself is technically non-conforming.
Why it matters
- Standards compliance: A valid HTML document requires all attribute values that expect URLs to contain properly encoded URIs. Raw brackets violate this requirement.
- Interoperability: While mainstream browsers handle this gracefully, other HTML consumers — such as screen readers, web scrapers, embedded webview components, or email clients — may not be as forgiving.
- Maintainability: Properly encoded URLs are unambiguous and reduce the risk of subtle parsing bugs, especially when URLs are constructed dynamically or passed through multiple layers of processing.
How to fix it
There are two main approaches:
Percent-encode the brackets. Replace every
[with%5Band every]with%5Din the URL. The server will decode them back to brackets automatically, so functionality is preserved.Use alternative parameter naming. If you control the server, switch to a naming convention that avoids brackets altogether, such as dot notation (
filters.category) or underscores (filters_category). This keeps the URL valid without any encoding.
If you're generating the URL dynamically in JavaScript, you can use encodeURIComponent() on individual parameter keys and values, or use the URL and URLSearchParams APIs, which handle encoding automatically.
Examples
Incorrect — raw brackets in the query string
<iframe src="https://example.com/embed?filters[category]=news&filters[tags]=web"></iframe>
The [ and ] characters in the query string make this an invalid URL value, triggering the validator error.
Fixed — percent-encoded brackets
<iframe src="https://example.com/embed?filters%5Bcategory%5D=news&filters%5Btags%5D=web"></iframe>
Replacing [ with %5B and ] with %5D produces a valid URL. The server receives the same parameter names after decoding.
Fixed — alternative parameter naming
<iframe src="https://example.com/embed?filters.category=news&filters.tags=web"></iframe>
If the server supports it, using dot notation eliminates the need for brackets entirely, keeping the URL clean and valid.
Generating encoded URLs in JavaScript
<iframe id="content-frame"></iframe>
<script>
const url = new URL("https://example.com/embed");
url.searchParams.set("filters[category]", "news");
url.searchParams.set("filters[tags]", "web");
document.getElementById("content-frame").src = url.href;
// Result: https://example.com/embed?filters%5Bcategory%5D=news&filters%5Btags%5D=web
</script>
The URLSearchParams API automatically percent-encodes reserved characters, so you can write the parameter names naturally and let the browser produce a valid URL.
A data: URI embeds resource content directly in a URL rather than pointing to an external file. It follows the format data:[<mediatype>][;base64],<data>. RFC 2397, which governs this scheme, explicitly states that fragment identifiers (the # character followed by a fragment name) are not valid in data: URIs.
This issue most commonly arises when developers try to reference a specific element inside an embedded SVG — for example, a particular <symbol> or element with an id — by appending a fragment like #icon-name to a data: URI. While fragments work in standard URLs (e.g., icons.svg#home), the data: URI scheme simply doesn't support them.
Why It Matters
- Standards compliance: Browsers may handle invalid
data:URIs inconsistently. Some may silently ignore the fragment, while others may fail to render the image entirely. - Portability: Code that relies on non-standard behavior in one browser may break in another or in a future update.
- Accessibility and tooling: Validators, linters, and assistive technologies expect well-formed URIs. An invalid URI can cause unexpected issues down the chain.
How to Fix It
You have several options depending on your use case:
- Remove the fragment from the
data:URI. If the embedded content is a complete, self-contained image, it doesn't need a fragment reference. - Inline the SVG directly into the HTML document. This lets you reference internal
idvalues with standard fragment syntax using<use>elements. - Use an external file instead of a
data:URI. Standard URLs likeicons.svg#homefully support fragment identifiers. - Encode the full, standalone SVG into the
data:URI so that it contains only the content you need, eliminating the need for a fragment reference.
Examples
❌ Incorrect: Fragment in a data: URI
<img
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Csymbol id='icon'%3E%3Ccircle cx='10' cy='10' r='10'/%3E%3C/symbol%3E%3C/svg%3E#icon"
alt="Icon">
The #icon fragment at the end of the data: URI violates RFC 2397 and triggers the validation error.
✅ Correct: Self-contained SVG in a data: URI (no fragment)
Embed only the content you need directly, without wrapping it in a <symbol> or referencing a fragment:
<img
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Ccircle cx='10' cy='10' r='10'/%3E%3C/svg%3E"
alt="Icon">
✅ Correct: External SVG file with a fragment
Move the SVG to a separate file and reference the specific symbol using a standard URL fragment:
<img src="icons.svg#icon" alt="Icon">
✅ Correct: Inline SVG with <use> referencing an internal id
If you need to reference individual symbols from a sprite, inline the SVG and use fragment references within the same document:
<svg xmlns="http://www.w3.org/2000/svg" hidden>
<symbol id="icon-home" viewBox="0 0 20 20">
<path d="M10 2 L2 10 L4 10 L4 18 L16 18 L16 10 L18 10 Z"/>
</symbol>
</svg>
<svg width="20" height="20" aria-hidden="true">
<use href="#icon-home"></use>
</svg>
This approach gives you full control over individual icons without needing data: URIs at all, and it's the most flexible option for icon systems.
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:
<img src="<%= user.avatar %>" alt="User avatar">
Incorrect — HTML accidentally inside src
Angle brackets from stray markup ended up in the URL:
<img src="images/<thumbnail>/photo.jpg" alt="Photo">
Correct — a valid relative URL
<img src="images/photo.jpg" alt="Photo">
Correct — a valid absolute URL
<img src="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:
<img src="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.
The src attribute on a <script> element tells the browser where to fetch an external JavaScript file. According to the HTML specification, when the src attribute is present, its value must be a valid non-empty URL. An empty string does not qualify as a valid URL, so the validator flags it as an error.
This issue typically arises in a few common scenarios:
- Templating or CMS placeholders — A template engine or content management system outputs an empty
srcwhen no script URL is configured. - Dynamic JavaScript — Client-side code is intended to set the
srclater, but the initial HTML ships with an empty value. - Copy-paste mistakes — The attribute was added in anticipation of a script file that was never specified.
Beyond failing validation, an empty src causes real problems. Most browsers interpret an empty src as a relative URL that resolves to the current page's URL. This means the browser will make an additional HTTP request to re-fetch the current HTML document and attempt to parse it as JavaScript, wasting bandwidth, slowing down page load, and potentially generating console errors. It can also cause unexpected side effects in server logs and analytics.
How to Fix It
Choose the approach that matches your intent:
- Provide a valid URL — If you need an external script, set
srcto the correct file path or full URL. - Use an inline script — If your JavaScript is written directly in the HTML, remove the
srcattribute entirely. Note that whensrcis present, browsers ignore any content between the opening and closing<script>tags. - Remove the element — If the script isn't needed, remove the
<script>element altogether.
Also keep in mind:
- Ensure the file path is correct relative to the HTML file's location.
- If using an absolute URL, verify it is accessible and returns JavaScript content.
- If a script should only be loaded conditionally, handle the condition in your server-side or build logic rather than outputting an empty
src.
Examples
❌ Invalid: Empty src attribute
<script src=""></script>
This triggers the validation error because the src value is an empty string.
❌ Invalid: Whitespace-only src attribute
<script src=" "></script>
A value containing only whitespace is also not a valid URL and will produce the same error.
✅ Fixed: Valid external script
<script src="js/app.js"></script>
The src attribute contains a valid relative URL pointing to an actual JavaScript file.
✅ Fixed: Valid external script with a full URL
<script src="https://example.com/js/library.min.js"></script>
✅ Fixed: Inline script without src
If you want to write JavaScript directly in your HTML, omit the src attribute:
<script>
console.log("This is an inline script.");
</script>
✅ Fixed: Conditionally omitting the element
If the script URL comes from a template variable that might be empty, handle it at the template level so the <script> element is only rendered when a URL is available. For example, in a templating language:
<!-- Pseudocode — only output the tag when the URL exists -->
<!-- {% if script_url %} -->
<script src="analytics.js"></script>
<!-- {% endif %} -->
This prevents the empty src from ever reaching the browser.
The srcset attribute allows you to provide multiple image sources so the browser can choose the most appropriate one based on the user's device and viewport. There are two distinct modes for srcset:
- Width descriptor mode — Each candidate specifies the image's intrinsic width using a
wdescriptor (e.g.,640w). This mode requires thesizesattribute so the browser knows how much space the image will occupy in the layout, enabling it to pick the best candidate. - Density descriptor mode — Each candidate specifies a pixel density using an
xdescriptor (e.g.,2x). This mode does not use thesizesattribute; the browser simply matches candidates to the device's pixel density.
These two modes are mutually exclusive. You cannot mix w and x descriptors in the same srcset, and you cannot use x descriptors (or bare URLs with no descriptor) when sizes is present. The HTML specification is explicit about this: if sizes is specified, all image candidate strings must include a width descriptor.
Why this matters
- Standards compliance: The WHATWG HTML Living Standard defines strict parsing rules for
srcset. Whensizesis present, the browser's source selection algorithm expects width descriptors. Providing density descriptors or bare candidates in this context violates the spec and produces undefined behavior. - Broken image selection: Browsers rely on the
sizesattribute to calculate whichw-described image best fits the current layout width. If you providexdescriptors alongsidesizes, the browser may ignore thesrcsetentirely or fall back to thesrcattribute, defeating the purpose of responsive images. - Accessibility and performance: Responsive images exist to serve appropriately sized files to different devices. An invalid
srcset/sizescombination can result in oversized images being downloaded on small screens (wasting bandwidth) or undersized images on large screens (reducing visual quality).
How to fix it
You have two options:
- Keep
sizesand switch to width descriptors — Replace everyxdescriptor (or missing descriptor) insrcsetwith the actual intrinsic pixel width of each image file, expressed with awsuffix. - Remove
sizesand keep density descriptors — If you only need to serve different resolutions for high-DPI screens at a fixed layout size, drop thesizesattribute and usexdescriptors.
When using width descriptors, the value must match the image file's actual intrinsic width in pixels. For example, if photo-640.jpg is 640 pixels wide, its descriptor should be 640w.
Examples
Invalid: sizes present with density descriptors
This triggers the error because 1x and 2x are density descriptors, but sizes requires width descriptors.
<img
src="photo-640.jpg"
srcset="photo-640.jpg 1x, photo-1280.jpg 2x"
sizes="(max-width: 600px) 100vw, 600px"
alt="A mountain landscape">
Invalid: sizes present with a bare candidate (no descriptor)
A candidate with no descriptor defaults to 1x, which is a density descriptor — still invalid when sizes is present.
<img
src="photo-640.jpg"
srcset="photo-640.jpg, photo-1280.jpg 2x"
sizes="(max-width: 600px) 100vw, 600px"
alt="A mountain landscape">
Fix: use width descriptors with sizes
Each candidate now specifies the intrinsic width of the image file. The browser uses the sizes value to determine which image to download.
<img
src="photo-640.jpg"
srcset="photo-320.jpg 320w, photo-640.jpg 640w, photo-1280.jpg 1280w"
sizes="(max-width: 600px) 100vw, 600px"
alt="A mountain landscape">
Alternative fix: remove sizes and use density descriptors
If you don't need the browser to choose images based on layout width — for example, the image always renders at a fixed CSS size — drop sizes and use x descriptors.
<img
src="photo-640.jpg"
srcset="photo-640.jpg 1x, photo-1280.jpg 2x"
alt="A mountain landscape">
Using width descriptors with source inside picture
The same rule applies to source elements inside a picture. If sizes is present, every candidate must use a w descriptor.
<picture>
<source
srcset="hero-480.webp 480w, hero-960.webp 960w, hero-1920.webp 1920w"
sizes="(max-width: 768px) 100vw, 50vw"
type="image/webp">
<img
src="hero-960.jpg"
srcset="hero-480.jpg 480w, hero-960.jpg 960w, hero-1920.jpg 1920w"
sizes="(max-width: 768px) 100vw, 50vw"
alt="A hero banner image">
</picture>
The type attribute on an anchor (<a>) element is an advisory hint that tells the browser what kind of content to expect at the link destination. According to the WHATWG HTML specification, when present, this attribute must contain a valid MIME type string. An empty string ("") is not a valid MIME type, so the W3C validator flags it as an error.
This issue commonly appears when a CMS, templating engine, or JavaScript framework generates the type attribute dynamically but produces an empty value when no MIME type is available. It can also happen when developers add the attribute as a placeholder intending to fill it in later.
Why this matters
While browsers generally handle an empty type attribute gracefully by ignoring it, there are good reasons to fix this:
- Standards compliance — An empty string violates the HTML specification's requirement for a valid MIME type, making your document invalid.
- Accessibility — Assistive technologies may use the
typeattribute to communicate the linked resource's format to users. An empty value provides no useful information and could lead to unexpected behavior in some screen readers. - Predictable behavior — Browsers are allowed to use the
typehint to influence how they handle a link (e.g., suggesting a download or choosing a handler). An empty value makes the intent ambiguous.
How to fix it
You have two straightforward options:
- Remove the
typeattribute — If you don't need to specify the MIME type, simply omit the attribute. This is the preferred approach when the type is unknown or unnecessary. - Provide a valid MIME type — If you want to hint at the linked resource's format, supply a proper MIME type string like
application/pdf,text/html,image/png,application/zip, etc.
The type attribute is purely advisory — the browser does not enforce it or refuse to follow the link if the actual content type differs. So omitting it is always safe.
Examples
Incorrect — empty type attribute
<a href="report.pdf" type="">Download Report</a>
This triggers the validation error because "" is not a valid MIME type.
Correct — attribute removed
<a href="report.pdf">Download Report</a>
Removing the type attribute entirely is the simplest fix and works perfectly when the MIME type hint isn't needed.
Correct — valid MIME type provided
<a href="report.pdf" type="application/pdf">Download Report</a>
If you want to explicitly indicate the format of the linked resource, use a proper MIME type value.
Multiple links with correct type usage
<ul>
<li><a href="slides.pptx" type="application/vnd.openxmlformats-officedocument.presentationml.presentation">Slides (PPTX)</a></li>
<li><a href="data.csv" type="text/csv">Data (CSV)</a></li>
<li><a href="https://example.com/about">About Us</a></li>
</ul>
In this example, the first two links include type attributes with valid MIME types to hint at the file format. The third link, pointing to a regular webpage, omits type entirely since it's not needed.
Fixing dynamically generated attributes
If your template engine produces empty type attributes, add a conditional check:
<!-- Instead of always outputting type="" -->
<!-- Only include the attribute when a value exists -->
<a href="report.pdf" type="application/pdf">Download Report</a>
In template logic, ensure you only render the type attribute when a non-empty MIME type value is available. Otherwise, omit the attribute from the output altogether.
The type attribute on a <source> element must contain a MIME type, so an empty value like type="" is invalid.
A <source> names one candidate file for its parent <video>, <audio> or <picture> element. The optional type attribute gives that file's format, such as video/mp4, audio/mpeg or image/avif, and the browser checks it before downloading anything. A <video> or <audio> element skips any source whose type it knows it cannot play. A <picture> element skips sources with an image type the browser does not support and moves on to the next one, ending at its <img>.
An empty type gives the browser nothing to check, so either write the file's MIME type or remove the attribute. Removing it is fine for <video> and <audio>, which find out the format by fetching the file. Inside <picture>, keep type on any source in a format some browsers can't decode, such as AVIF. Without it, those browsers pick the source anyway and show a broken image. If a template prints type on every source, make it print the attribute only when it has a value.
Check the src too. A link to a page on a video hosting service is not a media file, and no type value will make a <video> element play it. Use the service's <iframe> embed code for those videos.
Invalid example
<video controls>
<source src="intro.webm" type="">
<source src="intro.mp4" type="">
</video>
Valid example
<video controls>
<source src="intro.webm" type="video/webm">
<source src="intro.mp4" type="video/mp4">
</video>
The xmlns attribute has the XHTML namespace written with https://, but the only allowed value is http://www.w3.org/1999/xhtml.
A namespace name looks like a web address, yet no browser ever loads it. Parsers treat it as a fixed name and compare it character by character, so https://www.w3.org/1999/xhtml names a different namespace than the HTML one. The https usually slips in when a site moves to HTTPS. A search and replace across the templates, or a plugin that upgrades every http:// string, changes the namespace along with the real links. The SVG and MathML namespaces keep http:// too.
In a page served as text/html, the parser puts every element in the HTML namespace by itself, and the HTML standard says xmlns has no effect there, which is why the page still renders. The value matters once the same markup is parsed as XML, for example when it's served as application/xhtml+xml or run through XSLT. Its elements then land in an unknown namespace, and browsers and XML tools no longer treat them as HTML.
Change https back to http. In a page served as HTML you can delete the attribute instead, and keep it only if the same file also has to work as XHTML. If an HTTPS rewrite caused the change, exclude namespace names from it so the next run doesn't break them again.
Invalid example
<!DOCTYPE html>
<html xmlns="https://www.w3.org/1999/xhtml" lang="en">
<head>
<title>Products</title>
</head>
<body>
<p>Our product range.</p>
</body>
</html>
Valid example
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<title>Products</title>
</head>
<body>
<p>Our product range.</p>
</body>
</html>
The WAI-ARIA specification defines a specific set of role values that assistive technologies like screen readers understand. These include roles such as button, checkbox, alert, dialog, img, navigation, banner, and many others. The value "icon" is not among them. When a browser or assistive technology encounters an unrecognized role, it cannot determine the element's purpose, which defeats the goal of using ARIA in the first place.
This is primarily an accessibility problem. Screen readers rely on valid ARIA roles to communicate the nature of elements to users. An invalid role like "icon" is either ignored or causes unpredictable behavior, leaving users of assistive technologies without the context they need. It's also a standards compliance issue — the W3C validator flags this because the HTML specification requires role values to match roles defined in the ARIA specification.
The fix depends on the purpose of the element:
- Decorative icons (that don't convey information): Remove the
roleattribute entirely, or usearia-hidden="true"to explicitly hide the element from the accessibility tree. - Meaningful icons (that convey information visually): Use
role="img"along with anaria-labelto provide a text alternative. - Icons inside interactive elements: Hide the icon with
aria-hidden="true"and ensure the parent interactive element has an accessible name through visible text or anaria-label.
Examples
❌ Invalid: Using the non-existent "icon" role
<span class="icon" role="icon"></span>
This triggers the validation error because "icon" is not a valid ARIA role.
✅ Fixed: Decorative icon with no role
If the icon is purely decorative and doesn't convey any meaning (e.g., it's next to text that already describes the action), simply remove the role attribute. Adding aria-hidden="true" ensures screen readers skip over it completely.
<span class="icon" aria-hidden="true"></span>
✅ Fixed: Meaningful icon using role="img"
If the icon conveys meaningful information that isn't available through surrounding text, use role="img" and provide a descriptive aria-label:
<span class="icon-warning" role="img" aria-label="Warning"></span>
This tells assistive technologies that the element represents an image and gives it an accessible name of "Warning."
✅ Fixed: Icon inside a button
When an icon is placed inside an interactive element like a button, hide the icon from the accessibility tree and let the button's text or label provide the meaning:
<button>
<span class="icon-save" aria-hidden="true"></span>
Save
</button>
If the button has no visible text (an icon-only button), provide an aria-label on the button itself:
<button aria-label="Save">
<span class="icon-save" aria-hidden="true"></span>
</button>
✅ Fixed: Icon using an <img> element instead
If you're using an actual image file for the icon, consider using a semantic <img> element, which has a built-in img role:
<img src="icon-alert.svg" alt="Alert" class="icon">
For decorative image icons, use an empty alt attribute:
<img src="icon-decorative.svg" alt="" class="icon">
This mailto: link has a line break inside its href, and a URL can't contain a raw line break.
It usually happens when the attribute value is split across two lines in the source, often right after mailto:, by a template, a CMS field or an editor that wraps long lines. The validator says the line break comes "after ilto:", which means anywhere after the scheme: right after the colon, inside the address, or after the comma between two recipients. Indentation on the next line doesn't change the message, and an encoded line break such as triggers it too.
A line break at the very start or end of the value isn't reported, because leading and trailing whitespace is stripped from URLs. A line break inside the subject or body parameters gets a different message, "Tab, new line or carriage return found." To put a line break in the message body, encode it as %0D%0A.
Browsers remove line breaks from URLs before following them, so the link often still works, but the markup is invalid. Keep the whole mailto: URL on one line with no whitespace in it. If the tag gets too long, break the line between attributes instead of inside the value.
Invalid example
<a href="mailto:
info@example.com">Email us</a>
Valid example
<a class="contact-link"
href="mailto:info@example.com">Email us</a>
This mailto: link has a space somewhere in its address, and a URL can't contain a raw space.
The validator says the space comes "after ilto:", which means anywhere after the scheme, not only right after the colon. It flags a space typed after mailto:, a space inside the address, a space after the comma between two recipients, and text or markup left in the attribute after the address, such as mailto:info@example.com <br>. That last case shows up when a template or CMS field drops HTML into the link.
A mailto: URL, defined in RFC 6068, holds one or more bare email addresses after the colon. Some browsers open the mail client anyway, but the extra text can end up in the recipient field.
A space inside the subject or body parameters triggers a different message, "Illegal character in query". Encode those spaces as %20.
To fix it, remove the spaces and anything after the address that isn't part of it. Separate several recipients with a comma and no space.
Invalid example
<a href="mailto: info@example.com">Email us</a>
<a href="mailto:info@example.com, sales@example.com">Email us</a>
<a href="mailto:info@example.com <br>">Email us</a>
Valid example
<a href="mailto:info@example.com">Email us</a>
<a href="mailto:info@example.com,sales@example.com">Email us</a>
In HTML, every element has a set of ARIA roles it is allowed to carry. The ul element implicitly has the list role, and the ARIA specification only permits certain roles to override it — specifically directory, group, listbox, menu, menubar, none, presentation, radiogroup, tablist, toolbar, and tree. The navigation role is not among them.
The navigation role is a landmark role, meaning it identifies a major section of the page dedicated to navigational links. HTML5 introduced the nav element specifically for this purpose, and it carries the navigation role implicitly — no role attribute needed. When you place role="navigation" on a ul, you're conflicting with the element's semantics. A ul represents a list of items, not a navigational landmark. Assistive technologies like screen readers rely on correct role assignments to help users understand page structure and navigate efficiently. An incorrect role can confuse users by misrepresenting what the element actually is.
Beyond accessibility concerns, this is a standards compliance issue. The W3C validator enforces the rules defined in the ARIA in HTML specification, which maps each HTML element to its allowed roles. Violating these rules means your markup is invalid and may behave unpredictably across different browsers and assistive technologies.
The fix is straightforward: use a nav element as the wrapper for your navigation list. This gives you the navigation landmark semantics automatically, while the ul retains its proper list role. Both elements work together — the nav tells assistive technologies "this is a navigation section," and the ul tells them "here is a list of links."
Examples
❌ Incorrect: navigation role on a ul
This triggers the validation error because navigation is not an allowed role for ul.
<ul role="navigation">
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
✅ Correct: wrapping the ul in a nav element
The nav element provides the navigation landmark implicitly. No role attribute is needed.
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
✅ Correct: labeling multiple navigation landmarks
When a page has more than one nav element, use aria-label to distinguish them for screen reader users.
<nav aria-label="Main">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
<nav aria-label="Footer">
<ul>
<li><a href="/privacy">Privacy Policy</a></li>
<li><a href="/terms">Terms of Service</a></li>
</ul>
</nav>
✅ Correct: using an allowed role on ul
If you need the ul to behave as something other than a plain list — for example, a menu in a web application — use one of its permitted roles.
<ul role="menubar">
<li role="menuitem"><a href="/">Home</a></li>
<li role="menuitem"><a href="/about">About</a></li>
<li role="menuitem"><a href="/contact">Contact</a></li>
</ul>
Note that menu and menubar roles are meant for application-style menus with keyboard interaction, not for simple site navigation. For standard website navigation, the nav wrapper approach is almost always the right choice.
The autocorrect attribute on <input> elements does not accept "none" as a valid value. The correct value to disable autocorrection is "off".
The autocorrect attribute controls whether the browser should automatically correct text input. It accepts only two values: "on" to enable autocorrection and "off" to disable it. The value "none" is not part of the specification and will fail W3C validation.
This attribute was originally a non-standard Safari extension, but it has since been added to the HTML standard. It applies to <input> elements that accept text input (such as text, search, and url types), <textarea> elements, and elements with contenteditable set.
Note that autocorrect is different from autocomplete, which uses "off" and "on" as well, but also accepts many other token values like "name" or "email". The two attributes control different behaviors: autocorrect handles spelling correction, while autocomplete handles previously entered values and autofill.
Incorrect example
<input type="text" autocorrect="none">
Correct example
<input type="text" autocorrect="off">
The HTML specification defines a specific list of allowed ARIA roles for each element. For the <img> element, role="none" is permitted but role="presentation" is not listed as a valid value. This distinction exists even though the WAI-ARIA 1.1 specification treats none and presentation as synonymous — role="none" was introduced as an alias specifically because the word "presentation" was often misunderstood by authors. The HTML spec adopted none as the canonical value for <img>.
Why this matters
Standards compliance: Using a role value not permitted by the HTML specification for a given element produces a validation error. Keeping your HTML valid ensures predictable behavior across browsers and assistive technologies.
Accessibility: The intended purpose of role="presentation" or role="none" is to tell assistive technologies that an element is purely decorative and carries no semantic meaning. However, for images, the established and most reliable way to achieve this is simply providing an empty alt attribute (alt=""). Screen readers already know to skip images with alt="", so adding a role is usually unnecessary.
Clarity of intent: Using alt="" clearly communicates to both browsers and developers that the image is decorative. If the image actually conveys information, it should have a meaningful alt value and no presentation-related role at all.
How to fix it
- If the image is decorative: Remove the
roleattribute entirely and ensure the image hasalt="". This is the simplest and most widely supported approach. - If you need an explicit ARIA role: Replace
role="presentation"withrole="none"and keepalt="". - If the image conveys meaning: Remove the role and provide a descriptive
altattribute that explains what the image communicates.
Examples
❌ Bad: using role="presentation" on an <img>
<img src="divider.png" alt="" role="presentation">
This triggers the validation error because presentation is not an allowed role value for <img> in the HTML specification.
✅ Fixed: decorative image with empty alt (preferred)
<img src="divider.png" alt="">
The empty alt attribute is sufficient to tell assistive technologies the image is decorative. No role is needed.
✅ Fixed: decorative image with role="none"
<img src="divider.png" alt="" role="none">
If you explicitly need an ARIA role, role="none" is the valid value for <img>. The empty alt should still be included.
✅ Fixed: meaningful image with descriptive alt
<img src="quarterly-sales.png" alt="Bar chart showing quarterly sales increasing from $2M to $5M in 2024">
If the image communicates information, provide a descriptive alt and do not use a presentation or none role — doing so would hide the image's meaning from assistive technology users.
❌ Bad: meaningful image incorrectly hidden
<img src="quarterly-sales.png" alt="Sales chart" role="presentation">
This is both invalid HTML (wrong role value for <img>) and an accessibility problem — the role would attempt to hide a meaningful image from screen readers.
ARIA defines a closed set of role tokens. Browsers and assistive technologies rely on this fixed list to determine how an element should be announced and how it fits into the page's landmark structure. When a role value falls outside that list, the attribute is effectively ignored. A screen reader encountering role="sidebar" will not recognize the element as a landmark, so users who navigate by landmarks will skip right past it. Automated accessibility testing tools will also flag it as an error, and the validator will reject it outright.
The concept most people mean when they write role="sidebar" is ancillary or tangential content related to the main content of the page. ARIA calls this the complementary role. In semantic HTML, the <aside> element maps to complementary without any explicit role attribute. Using <aside> is the simplest fix and keeps the markup clean.
A few things to keep in mind when choosing the replacement:
- If the sidebar contains related content, promotional links, or supplementary information,
complementary(via<aside>orrole="complementary") is correct. - If the sidebar is actually a set of navigation links, use
<nav>orrole="navigation"instead. Pick the role that matches the content's purpose, not its visual position. - When a page has more than one
complementaryregion, each one needs a distinct accessible name so screen reader users can tell them apart. Usearia-labelledbypointing to a visible heading, oraria-labelif no heading is present. - Do not add
role="complementary"to an<aside>element. The implicit mapping already handles it, and the redundant attribute adds noise without benefit.
Examples
Invalid: non-existent ARIA role
This triggers the validator error because sidebar is not a defined ARIA role.
<div role="sidebar">
<h2>Related links</h2>
<ul>
<li><a href="/guide-a">Guide A</a></li>
<li><a href="/guide-b">Guide B</a></li>
</ul>
</div>
Fixed: use role="complementary" on a generic container
If you need to keep the <div>, assign the correct ARIA role and provide an accessible name.
<div role="complementary" aria-labelledby="sidebar-title">
<h2 id="sidebar-title">Related links</h2>
<ul>
<li><a href="/guide-a">Guide A</a></li>
<li><a href="/guide-b">Guide B</a></li>
</ul>
</div>
Fixed: use <aside> for semantic HTML
The <aside> element has an implicit complementary role, so no role attribute is needed.
<aside aria-labelledby="sidebar-title">
<h2 id="sidebar-title">Related links</h2>
<ul>
<li><a href="/guide-a">Guide A</a></li>
<li><a href="/guide-b">Guide B</a></li>
</ul>
</aside>
Multiple complementary regions with distinct labels
When a page has more than one <aside>, give each a unique accessible name so users can distinguish them.
<aside aria-labelledby="filters-title">
<h2 id="filters-title">Filter results</h2>
<!-- filter controls -->
</aside>
<aside aria-labelledby="related-title">
<h2 id="related-title">Related articles</h2>
<!-- related links -->
</aside>
When the content is navigation, not complementary
A sidebar that contains section links or site links is navigation, not complementary content. Use <nav> instead.
<nav aria-label="Section navigation">
<ul>
<li><a href="#intro">Intro</a></li>
<li><a href="#examples">Examples</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</nav>
Valid ARIA landmark roles
For reference, these are the ARIA landmark roles that browsers and assistive technologies recognize:
banner(implicit on<header>when not nested inside a sectioning element)navigation(implicit on<nav>)main(implicit on<main>)complementary(implicit on<aside>)contentinfo(implicit on<footer>when not nested inside a sectioning element)region(implicit on<section>when it has an accessible name)search(implicit on<search>)form(implicit on<form>when it has an accessible name)
Stick to these defined values. Inventing role names like sidebar, content, or wrapper will always produce a validation error and provide no accessibility benefit.
ARIA defines a fixed set of role values that user agents and assistive technologies understand. sidebar is not in that set, so role="sidebar" fails conformance checking and gives unreliable signals to screen readers. Using a valid role or the correct HTML element improves accessibility, ensures consistent behavior across browsers and AT, and keeps your markup standards‑compliant.
Sidebars typically contain tangential or ancillary content (e.g., related links, promos, author info). The ARIA role that matches that meaning is complementary. In HTML, the semantic element for the same concept is aside, which by default maps to the complementary landmark in accessibility APIs. Prefer native semantics first: use <aside> when possible. Only add role="complementary" when you can’t change the element type or when you need an explicit landmark for non-semantic containers.
How to fix:
- If the element is a sidebar: change
<div role="sidebar">to<aside>(preferred), or to<div role="complementary">. - Ensure each page has at most one primary
mainregion and that complementary regions are not essential to understanding the main content. - Provide an accessible name for the complementary region when multiple exist, using
aria-labeloraria-labelledby, to help users navigate landmarks.
Examples
Triggers the validator error
<div role="sidebar">
<!-- Sidebar content -->
</div>
Fixed: use the semantic element (preferred)
<aside aria-label="Related articles">
<!-- Sidebar content -->
</aside>
Fixed: keep the container, apply a valid role
<div role="complementary" aria-label="Related articles">
<!-- Sidebar content -->
</div>
Full document example with two sidebars (each labeled)
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Sidebar Landmarks Example</title>
</head>
<body>
<header>
<h1>News Today</h1>
</header>
<main id="main">
<article>
<h2>Main Story</h2>
<p>...</p>
</article>
</main>
<aside aria-label="Trending topics">
<ul>
<li>Science</li>
<li>Politics</li>
<li>Sports</li>
</ul>
</aside>
<div role="complementary" aria-labelledby="sponsor-title">
<h2 id="sponsor-title">Sponsored</h2>
<p>Ad content</p>
</div>
<footer>
<p>© 2026</p>
</footer>
</body>
</html>
Notes:
- Do not invent ARIA roles (e.g.,
sidebar,hero,footer-nav). Use defined roles likecomplementary,navigation,banner,contentinfo, andmain. - Prefer native HTML elements (
aside,nav,header,footer,main) over generic containers with roles. - Label multiple complementary landmarks to make them distinguishable in screen reader landmark lists.
A <ul> element can't have role="tab": put role="tablist" on the list and role="tab" on each tab inside it.
An ARIA tab interface uses three roles. tablist is the container, tab is each label the user clicks, and tabpanel is the content a tab shows. The tab role describes one control, so it doesn't fit a list that holds several of them. The validator checks roles against the ARIA in HTML specification, which allows only a short set of roles on <ul>. tablist is one of them and tab isn't.
In most cases the markup has the right idea and the wrong value: role="tab" was meant to be role="tablist". As written, screen readers announce the whole list as a single tab named after all of its items. The validator usually reports a second error on the same element, because a tab must sit inside a tablist.
Make each tab a <button>, which gets keyboard focus and Enter and Space activation without extra code. Give each <li> role="presentation" to remove its list item role, so the buttons count as direct children of the tablist. If you don't need list markup, a <div role="tablist"> that holds the buttons is simpler. The roles only change what assistive technologies announce. Your script still has to show the right panel, update aria-selected, and move focus between tabs with the arrow keys.
Invalid example
<ul role="tab">
<li><a href="#overview">Overview</a></li>
<li><a href="#pricing">Pricing</a></li>
</ul>
Valid example
<ul role="tablist" aria-label="Product details">
<li role="presentation">
<button type="button" role="tab" id="tab-overview" aria-selected="true" aria-controls="panel-overview">Overview</button>
</li>
<li role="presentation">
<button type="button" role="tab" id="tab-pricing" aria-selected="false" aria-controls="panel-pricing" tabindex="-1">Pricing</button>
</li>
</ul>
<div role="tabpanel" id="panel-overview" aria-labelledby="tab-overview">
<p>A short summary of the product.</p>
</div>
<div role="tabpanel" id="panel-pricing" aria-labelledby="tab-pricing" hidden>
<p>Plans start at $10 per month.</p>
</div>
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.
1K Weekly
1,000 monthly checks. Cancel anytime.
Get 1K Weekly →Join teams across 40+ countries