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.
Square brackets ([ and ]) are reserved characters under RFC 3986, the standard that defines URI syntax. They are only permitted in the host component of a URL (specifically for IPv6 addresses) and are illegal elsewhere, including the query string. While most browsers are lenient and will load an <iframe> even when the src contains raw brackets, the W3C HTML Validator correctly flags this as an invalid URL value.
This pattern surfaces frequently when working with frameworks or APIs that use bracket notation to represent arrays or nested objects in query parameters — for example, filters[category]=news or items[0]=apple. These URLs work in a browser's address bar because browsers silently fix malformed URLs, but the raw HTML itself is technically non-conforming.
Why it matters
- Standards compliance: A valid HTML document requires all attribute values that expect URLs to contain properly encoded URIs. Raw brackets violate this requirement.
- Interoperability: While mainstream browsers handle this gracefully, other HTML consumers — such as screen readers, web scrapers, embedded webview components, or email clients — may not be as forgiving.
- Maintainability: Properly encoded URLs are unambiguous and reduce the risk of subtle parsing bugs, especially when URLs are constructed dynamically or passed through multiple layers of processing.
How to fix it
There are two main approaches:
Percent-encode the brackets. Replace every
[with%5Band every]with%5Din the URL. The server will decode them back to brackets automatically, so functionality is preserved.Use alternative parameter naming. If you control the server, switch to a naming convention that avoids brackets altogether, such as dot notation (
filters.category) or underscores (filters_category). This keeps the URL valid without any encoding.
If you're generating the URL dynamically in JavaScript, you can use encodeURIComponent() on individual parameter keys and values, or use the URL and URLSearchParams APIs, which handle encoding automatically.
Examples
Incorrect — raw brackets in the query string
<iframesrc="https://example.com/embed?filters[category]=news&filters[tags]=web"></iframe>
The [ and ] characters in the query string make this an invalid URL value, triggering the validator error.
Fixed — percent-encoded brackets
<iframesrc="https://example.com/embed?filters%5Bcategory%5D=news&filters%5Btags%5D=web"></iframe>
Replacing [ with %5B and ] with %5D produces a valid URL. The server receives the same parameter names after decoding.
Fixed — alternative parameter naming
<iframesrc="https://example.com/embed?filters.category=news&filters.tags=web"></iframe>
If the server supports it, using dot notation eliminates the need for brackets entirely, keeping the URL clean and valid.
Generating encoded URLs in JavaScript
<iframeid="content-frame"></iframe>
<script>
consturl=newURL("https://example.com/embed");
url.searchParams.set("filters[category]","news");
url.searchParams.set("filters[tags]","web");
document.getElementById("content-frame").src=url.href;
// Result: https://example.com/embed?filters%5Bcategory%5D=news&filters%5Btags%5D=web
</script>
The URLSearchParams API automatically percent-encodes reserved characters, so you can write the parameter names naturally and let the browser produce a valid URL.
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:
<imgsrc="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:
<svgxmlns="http://www.w3.org/2000/svg"hidden>
<symbolid="icon-home"viewBox="0 0 20 20">
<pathd="M10 2 L2 10 L4 10 L4 18 L16 18 L16 10 L18 10 Z"/>
</symbol>
</svg>
<svgwidth="20"height="20"aria-hidden="true">
<usehref="#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:
<imgsrc="<%= user.avatar %>"alt="User avatar">
Incorrect — HTML accidentally inside src
Angle brackets from stray markup ended up in the URL:
<imgsrc="images/<thumbnail>/photo.jpg"alt="Photo">
Correct — a valid relative URL
<imgsrc="images/photo.jpg"alt="Photo">
Correct — a valid absolute URL
<imgsrc="https://example.com/images/photo.jpg"alt="Photo">
Correct — special characters percent-encoded
If the URL genuinely requires characters that are not allowed in a URI, percent-encode them:
<imgsrc="https://example.com/search?q=a%3Cb"alt="Search result">
In this case, %3C represents the < character in the query string, making the URI valid.
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
<scriptsrc=""></script>
This triggers the validation error because the src value is an empty string.
❌ Invalid: Whitespace-only src attribute
<scriptsrc=""></script>
A value containing only whitespace is also not a valid URL and will produce the same error.
✅ Fixed: Valid external script
<scriptsrc="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
<scriptsrc="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 %} -->
<scriptsrc="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
<ahref="report.pdf"type="">Download Report</a>
This triggers the validation error because "" is not a valid MIME type.
Correct — attribute removed
<ahref="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
<ahref="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><ahref="slides.pptx"type="application/vnd.openxmlformats-officedocument.presentationml.presentation">Slides (PPTX)</a></li>
<li><ahref="data.csv"type="text/csv">Data (CSV)</a></li>
<li><ahref="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 -->
<ahref="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 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
<spanclass="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.
<spanclass="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:
<spanclass="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>
<spanclass="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:
<buttonaria-label="Save">
<spanclass="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:
<imgsrc="icon-alert.svg"alt="Alert"class="icon">
For decorative image icons, use an empty alt attribute:
<imgsrc="icon-decorative.svg"alt=""class="icon">
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.
<ulrole="navigation">
<li><ahref="/">Home</a></li>
<li><ahref="/about">About</a></li>
<li><ahref="/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><ahref="/">Home</a></li>
<li><ahref="/about">About</a></li>
<li><ahref="/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.
<navaria-label="Main">
<ul>
<li><ahref="/">Home</a></li>
<li><ahref="/about">About</a></li>
<li><ahref="/contact">Contact</a></li>
</ul>
</nav>
<navaria-label="Footer">
<ul>
<li><ahref="/privacy">Privacy Policy</a></li>
<li><ahref="/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.
<ulrole="menubar">
<lirole="menuitem"><ahref="/">Home</a></li>
<lirole="menuitem"><ahref="/about">About</a></li>
<lirole="menuitem"><ahref="/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 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>
<imgsrc="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)
<imgsrc="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"
<imgsrc="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
<imgsrc="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
<imgsrc="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.
<divrole="sidebar">
<h2>Related links</h2>
<ul>
<li><ahref="/guide-a">Guide A</a></li>
<li><ahref="/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.
<divrole="complementary"aria-labelledby="sidebar-title">
<h2id="sidebar-title">Related links</h2>
<ul>
<li><ahref="/guide-a">Guide A</a></li>
<li><ahref="/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.
<asidearia-labelledby="sidebar-title">
<h2id="sidebar-title">Related links</h2>
<ul>
<li><ahref="/guide-a">Guide A</a></li>
<li><ahref="/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.
<asidearia-labelledby="filters-title">
<h2id="filters-title">Filter results</h2>
<!-- filter controls -->
</aside>
<asidearia-labelledby="related-title">
<h2id="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.
<navaria-label="Section navigation">
<ul>
<li><ahref="#intro">Intro</a></li>
<li><ahref="#examples">Examples</a></li>
<li><ahref="#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
<divrole="sidebar">
<!-- Sidebar content -->
</div>
Fixed: use the semantic element (preferred)
<asidearia-label="Related articles">
<!-- Sidebar content -->
</aside>
Fixed: keep the container, apply a valid role
<divrole="complementary"aria-label="Related articles">
<!-- Sidebar content -->
</div>
Full document example with two sidebars (each labeled)
<!doctype html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>Sidebar Landmarks Example</title>
</head>
<body>
<header>
<h1>News Today</h1>
</header>
<mainid="main">
<article>
<h2>Main Story</h2>
<p>...</p>
</article>
</main>
<asidearia-label="Trending topics">
<ul>
<li>Science</li>
<li>Politics</li>
<li>Sports</li>
</ul>
</aside>
<divrole="complementary"aria-labelledby="sponsor-title">
<h2id="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.
The W3C validator raises this error because ARIA roles must be compatible with the element they are applied to. A <ul> element has an implicit ARIA role of list, and overriding it with tabpanel creates a conflict. The tabpanel role signals to assistive technologies that the element is a panel of content activated by a corresponding tab. When this role is placed on a <ul>, screen readers lose the semantic meaning of the list (item count, list navigation, etc.) while also misrepresenting the element's function in the tab interface.
This matters for several reasons:
- Accessibility: Screen reader users rely on correct roles to navigate and understand page structure. A
<ul>marked astabpanelconfuses both its list semantics and its role in the tab interface. - Standards compliance: The ARIA in HTML specification defines which roles are allowed on which elements. The
tabpanelrole is not permitted on<ul>. - Browser behavior: Browsers may handle conflicting roles inconsistently, leading to unpredictable behavior across assistive technologies.
The fix is straightforward: wrap the <ul> inside a proper container element (like a <div> or <section>) and apply the tabpanel role to that container instead.
Examples
Incorrect: tabpanel role on a <ul>
This triggers the validation error because tabpanel is not a valid role for <ul>:
<divrole="tablist"aria-label="Recipe categories">
<buttonrole="tab"aria-controls="panel-1"aria-selected="true"id="tab-1">Appetizers</button>
<buttonrole="tab"aria-controls="panel-2"aria-selected="false"id="tab-2">Desserts</button>
</div>
<ulrole="tabpanel"id="panel-1"aria-labelledby="tab-1">
<li>Bruschetta</li>
<li>Spring rolls</li>
</ul>
<ulrole="tabpanel"id="panel-2"aria-labelledby="tab-2"hidden>
<li>Tiramisu</li>
<li>Cheesecake</li>
</ul>
Correct: tabpanel role on a container wrapping the <ul>
Move the tabpanel role to a <div> and nest the <ul> inside it. This preserves both the tab panel semantics and the list semantics:
<divrole="tablist"aria-label="Recipe categories">
<buttonrole="tab"aria-controls="panel-1"aria-selected="true"id="tab-1">Appetizers</button>
<buttonrole="tab"aria-controls="panel-2"aria-selected="false"id="tab-2">Desserts</button>
</div>
<divrole="tabpanel"id="panel-1"aria-labelledby="tab-1">
<ul>
<li>Bruschetta</li>
<li>Spring rolls</li>
</ul>
</div>
<divrole="tabpanel"id="panel-2"aria-labelledby="tab-2"hidden>
<ul>
<li>Tiramisu</li>
<li>Cheesecake</li>
</ul>
</div>
Correct: Using <section> as the tab panel
A <section> element also works well as a tab panel container, especially when the panel content is more complex:
<divrole="tablist"aria-label="Project info">
<buttonrole="tab"aria-controls="tasks-panel"aria-selected="true"id="tasks-tab">Tasks</button>
<buttonrole="tab"aria-controls="notes-panel"aria-selected="false"id="notes-tab">Notes</button>
</div>
<sectionrole="tabpanel"id="tasks-panel"aria-labelledby="tasks-tab">
<h2>Current tasks</h2>
<ul>
<li>Review pull requests</li>
<li>Update documentation</li>
</ul>
</section>
<sectionrole="tabpanel"id="notes-panel"aria-labelledby="notes-tab"hidden>
<h2>Meeting notes</h2>
<p>Discussed project timeline and milestones.</p>
</section>
In a properly structured tabbed interface:
- The
tablistrole goes on the container that holds the tab buttons. - Each tab trigger gets
role="tab"witharia-controlspointing to its panel'sid. - Each content panel gets
role="tabpanel"on a generic container like<div>or<section>, witharia-labelledbyreferencing the corresponding tab'sid. - List elements like
<ul>and<ol>should remain inside the panel as regular content, retaining their native list semantics.
The aria-hidden attribute controls whether an element and its descendants are exposed to assistive technologies such as screen readers. When set to true, the element is hidden from the accessibility tree; when set to false, it remains visible. According to the WAI-ARIA specification, the only valid values for this attribute are the literal strings true and false. Any other value — including "true" with embedded quotation marks — is invalid.
When the validator reports a bad value like "true" (with the quotation marks as part of the value), it means the actual attribute value contains the characters "true" rather than just true. HTML attributes already use outer quotes as delimiters, so any quotes inside the value become part of the value itself. The browser or assistive technology may not recognize "true" as a valid ARIA state, which can lead to the element being incorrectly exposed to or hidden from screen readers, breaking the intended accessibility behavior.
This issue commonly arises in a few scenarios:
- Copy-pasting from formatted text where "smart quotes" or extra quoting gets included.
- Templating engines or frameworks that double-escape or double-quote attribute values (e.g.,
aria-hidden="{{value}}"where{{value}}already outputs"true"). - JavaScript that sets the attribute with extra quotes, such as
element.setAttribute("aria-hidden", '"true"').
To fix the issue, ensure the attribute value contains only the bare string true or false with no extra quotation marks, HTML entities, or escaped characters inside it.
Examples
Incorrect — extra quotes embedded in the value
<divaria-hidden='"true"'>
This content should be hidden from assistive tech
</div>
The rendered attribute value is literally "true" (five characters including the quotes), which is not a recognized ARIA value.
Incorrect — HTML entities producing extra quotes
<divaria-hidden=""true"">
This content should be hidden from assistive tech
</div>
The " entities resolve to quotation mark characters, producing the same invalid value of "true".
Correct — simple true value
<divaria-hidden="true">
This content is hidden from assistive tech
</div>
Correct — simple false value
<divaria-hidden="false">
This content is visible to assistive tech
</div>
Fixing the issue in JavaScript
If you're setting the attribute dynamically, make sure you aren't wrapping the value in extra quotes:
<divid="modal">Modal content</div>
<script>
// Incorrect:
// document.getElementById("modal").setAttribute("aria-hidden", '"true"');
// Correct:
document.getElementById("modal").setAttribute("aria-hidden","true");
</script>
Fixing the issue in templating engines
If a template variable already outputs a quoted string, don't add additional quotes around it. For example, in a templating system:
<!-- Incorrect: if myVar outputs "true" (with quotes) -->
<!-- <div aria-hidden="{{myVar}}"> -->
<!-- Correct: ensure myVar outputs just true (no quotes) -->
<divaria-hidden="true">
Content
</div>
The key takeaway is straightforward: the outer quotes in aria-hidden="true" are HTML syntax — they delimit the attribute value. The value itself must be exactly true or false with nothing extra. If you're generating HTML dynamically, inspect the rendered output in your browser's developer tools to confirm the attribute value doesn't contain stray quotation marks.
The wrap attribute on a <textarea> controls how text is wrapped when the form is submitted. The value "virtual" was used by some older browsers (notably early versions of Netscape and Internet Explorer) as a proprietary alternative to what the HTML Standard now calls "soft". Since "virtual" was never part of any formal HTML specification, the W3C validator correctly rejects it as an invalid value.
The HTML Standard defines only two valid values for wrap:
soft(the default): The text is visually wrapped in the browser for display purposes, but no actual line break characters are inserted into the submitted form data. The server receives the text as continuous lines.hard: The browser inserts carriage return + line feed (CRLF) characters at the visual wrap points when the form is submitted, so the server receives the text with hard line breaks. When usingwrap="hard", you must also specify thecolsattribute so the browser knows where the wrap points are.
Since "virtual" was functionally identical to "soft", replacing it is straightforward. If you omit the wrap attribute altogether, the browser defaults to soft wrapping, which gives you the same behavior.
Why this matters
Using non-standard attribute values can lead to unpredictable behavior across browsers. While most modern browsers will likely fall back to soft wrapping when they encounter an unrecognized wrap value, this is not guaranteed by any specification. Sticking to valid values ensures consistent, cross-browser behavior and keeps your markup standards-compliant.
How to fix it
- Replace
wrap="virtual"withwrap="soft"for an explicit equivalent. - Remove the
wrapattribute entirely if you want the default soft wrapping behavior. - Use
wrap="hard"with acolsattribute if you actually need hard line breaks inserted on submission.
Examples
❌ Invalid: using the non-standard "virtual" value
<form>
<labelfor="msg">Message</label>
<textareaid="msg"name="msg"wrap="virtual"></textarea>
</form>
This triggers the error: Bad value "virtual" for attribute "wrap" on element "textarea".
✅ Fixed: using wrap="soft" (equivalent to "virtual")
<form>
<labelfor="msg">Message</label>
<textareaid="msg"name="msg"wrap="soft"></textarea>
</form>
✅ Fixed: omitting wrap entirely (defaults to "soft")
<form>
<labelfor="msg">Message</label>
<textareaid="msg"name="msg"></textarea>
</form>
Since "soft" is the default, removing the attribute produces identical behavior and cleaner markup.
✅ Fixed: using wrap="hard" with cols
If you need the submitted text to include line breaks at wrap points, use wrap="hard" and specify cols:
<form>
<labelfor="msg">Message</label>
<textareaid="msg"name="msg"wrap="hard"cols="60"rows="6"></textarea>
</form>
Note that cols is required when using wrap="hard". Omitting it will trigger a separate validation error.
Other legacy values to watch for
The value "virtual" isn't the only non-standard wrap value from the early web. You may also encounter wrap="physical" (the legacy equivalent of "hard") or wrap="off" (which disabled wrapping). Neither is valid in modern HTML. Replace "physical" with "hard" (and add cols), and replace "off" by removing the attribute and using CSS (white-space: nowrap; or overflow-wrap: normal;) to control visual wrapping if needed.
The HTML specification defines boolean attributes as attributes whose presence indicates a true state and whose absence indicates false. According to the WHATWG HTML standard, a boolean attribute may only have three valid representations:
- The attribute name alone (e.g.,
async) - The attribute with an empty string value (e.g.,
async="") - The attribute with a value matching its own name, case-insensitively (e.g.,
async="async")
Any other value — such as async="true", async="1", async="yes", or async="false" — is invalid HTML and will trigger this validation error. This is a common misunderstanding because developers often assume boolean attributes work like boolean values in programming languages, where you'd assign true or false.
Why this matters
While most browsers are lenient and will treat any value of async as the true state (since the attribute is present regardless of its value), using invalid values creates several problems:
- Standards compliance: Invalid HTML may cause issues with strict parsers, validators, or tools that process your markup.
- Misleading intent: Writing
async="false"does not disable async behavior — the attribute is still present, so the browser treats it as enabled. This can lead to confusing bugs where a script behaves asynchronously even though the developer intended otherwise. - Maintainability: Other developers reading the code may misinterpret
async="false"as actually disabling async loading.
To disable async behavior, you must remove the attribute entirely rather than setting it to "false".
How async works
For classic scripts with a src attribute, the async attribute causes the script to be fetched in parallel with HTML parsing and executed as soon as it's available, without waiting for the document to finish parsing.
For module scripts (type="module"), the async attribute causes the module and all its dependencies to be fetched in parallel and executed as soon as they are ready, rather than waiting until the document has been parsed (which is the default deferred behavior for modules).
Examples
❌ Invalid: arbitrary values on async
<!-- Bad: "true" is not a valid boolean attribute value -->
<scriptasync="true"src="app.js"></script>
<!-- Bad: "1" is not a valid boolean attribute value -->
<scriptasync="1"src="analytics.js"></script>
<!-- Bad: "yes" is not a valid boolean attribute value -->
<scriptasync="yes"src="tracker.js"></script>
<!-- Bad and misleading: this does NOT disable async -->
<scriptasync="false"src="app.js"></script>
✅ Valid: correct boolean attribute usage
<!-- Preferred: attribute name alone -->
<scriptasyncsrc="app.js"></script>
<!-- Also valid: empty string value -->
<scriptasync=""src="app.js"></script>
<!-- Also valid: value matching attribute name -->
<scriptasync="async"src="app.js"></script>
<!-- Correct way to disable async: remove the attribute -->
<scriptsrc="app.js"></script>
✅ Valid: async with module scripts
<scriptasynctype="module"src="app.mjs"></script>
<scriptasynctype="module">
import{init}from'./utils.mjs';
init();
</script>
This same rule applies to all boolean attributes in HTML, including defer, disabled, checked, required, hidden, and others. When in doubt, use the attribute name on its own with no value — it's the cleanest and most widely recognized form.
The HTML specification defines the height and width attributes on the <embed> element as valid non-negative integers. This means the value must consist only of digits — for example, 650 — with no units, whitespace, or other characters appended. When you write height="650px", the validator encounters the letter "p" where it expects either another digit or the end of the value, and it raises this error.
This is a common mistake because CSS requires units (e.g., 650px), and it's easy to assume HTML attributes work the same way. They don't. In HTML, the height attribute implicitly means CSS pixels, so writing 650 already means "650 pixels." Adding px is not only redundant — it makes the value invalid.
While most browsers are forgiving and will parse 650px correctly by stripping the unit, relying on this behavior is problematic. It violates the HTML specification, causes validation errors that can mask other real issues in your markup, and there's no guarantee every browser or embedded content handler will be equally tolerant. Standards compliance ensures consistent rendering across browsers and assistive technologies.
How to fix it
You have two approaches:
Remove the unit from the HTML attribute. Change
height="650px"toheight="650". This is the simplest fix and keeps your sizing in the markup.Move sizing to CSS. Remove the
heightattribute entirely and use a stylesheet or inlinestyleattribute instead. This approach is more flexible because CSS supports units like%,em,vh, and more.
The same rule applies to the width attribute on <embed>, as well as height and width on elements like <img>, <video>, <iframe>, and <canvas> — all of which expect plain integers in HTML.
Examples
❌ Invalid: unit included in the HTML attribute
The px suffix causes the validator error because the attribute value must be digits only.
<embedsrc="file.pdf"type="application/pdf"width="800"height="650px">
Other invalid variations include:
<embedsrc="file.pdf"type="application/pdf"height="100%">
<embedsrc="file.pdf"type="application/pdf"height="40em">
<embedsrc="file.pdf"type="application/pdf"height="50vh">
✅ Fixed: plain integer without a unit
Remove the unit so the value is a valid non-negative integer.
<embedsrc="file.pdf"type="application/pdf"width="800"height="650">
✅ Fixed: sizing moved to CSS
If you need units other than pixels, or prefer to keep presentation in your stylesheets, use CSS instead of the HTML attribute.
<embedclass="pdf-viewer"src="file.pdf"type="application/pdf">
.pdf-viewer{
width:800px;
height:650px;
}
✅ Fixed: inline style as an alternative
You can also use the style attribute directly if a separate stylesheet isn't practical.
<embedsrc="file.pdf"type="application/pdf"style="width:800px;height:80vh;">
This is especially useful when you need viewport-relative or percentage-based sizing that HTML attributes can't express.
The href attribute on an <a> element must contain a valid URL as defined by the WHATWG URL Standard. According to this standard, the forward slash (/) is the only recognized path segment delimiter. Backslashes (\) have no defined role in URL path syntax and are treated as invalid characters by the validator.
This issue most commonly occurs when developers copy file paths from Windows, which uses backslashes as its native path separator, directly into HTML. For example, copying a path like images\photos\sunset.jpg from Windows Explorer and pasting it into an href attribute will trigger this validation error.
While most modern browsers will silently normalize backslashes to forward slashes, relying on this behavior is problematic for several reasons:
- Standards compliance: Your HTML fails validation, which can mask other real issues in your code.
- Interoperability: Not all HTTP clients, crawlers, or tools normalize backslashes. Search engine bots, link checkers, or older browsers may fail to follow the link correctly.
- Portability: Code that depends on browser error correction is fragile and may break in unexpected environments, such as server-side rendering, email clients, or embedded web views.
- Accessibility: Screen readers and assistive technologies that parse
hrefvalues may not handle backslashes consistently, potentially breaking navigation for users who rely on these tools.
To fix the issue, simply replace every backslash (\) with a forward slash (/) in any URL used in an href attribute. This applies not only to <a> elements but to any attribute that expects a URL, such as src, action, or data.
Examples
Incorrect: backslash used as path delimiter
<ahref="docs\guide\intro.html">Introduction</a>
Correct: forward slash used as path delimiter
<ahref="docs/guide/intro.html">Introduction</a>
Incorrect: backslashes in an absolute URL
<ahref="https://example.com\blog\2024\post.html">Read the post</a>
Correct: forward slashes in an absolute URL
<ahref="https://example.com/blog/2024/post.html">Read the post</a>
Incorrect: mixed slashes
Sometimes a URL contains a mix of forward and backslashes, which still triggers the error:
<ahref="assets/images\photo.jpg">View photo</a>
Correct: all forward slashes
<ahref="assets/images/photo.jpg">View photo</a>
Tips for avoiding this issue
- Search and replace: If you're migrating content or working with paths generated on Windows, do a global find-and-replace of
\with/across your HTML files. - Editor settings: Many code editors can highlight or auto-fix invalid URL characters. Enable linting tools or HTML validation plugins to catch this early.
- Build tools: If your build process generates links from file system paths, ensure it normalizes path separators to forward slashes before writing them into HTML output.
- URL encoding: If you genuinely need a literal backslash character within a URL (which is extremely rare), it must be percent-encoded as
%5C. However, this is almost never the intended behavior when this validation error appears.
A valid URL consists of several parts: a scheme (like https), followed by ://, then the host, and optionally a path, query string, and fragment. The :// separator — a colon followed by two forward slashes — is a required part of the URL syntax for schemes like http and https. When one of these slashes is missing, the browser may fail to navigate to the intended destination, interpret the value as a relative path, or behave unpredictably across different environments.
This error commonly occurs due to simple typos, copy-paste mistakes, or programmatic URL construction where string concatenation goes wrong. While some browsers may attempt to correct malformed URLs, you should never rely on this behavior. A malformed href can break navigation entirely, cause security warnings, produce unexpected redirects, or confuse assistive technologies like screen readers that announce link destinations to users.
Beyond the missing-slash case, this error can also appear when other parts of the URL contain characters that aren't valid without proper encoding — for instance, spaces or special characters that should be percent-encoded. Always ensure your URLs conform to the URL Standard.
How to Fix
- Check the scheme separator: Verify that the protocol is followed by
://(colon and two slashes). For example,https://nothttps:/orhttps:. - Validate the full URL: Paste the URL into a browser's address bar to confirm it resolves correctly.
- Encode special characters: If the URL contains spaces or special characters, use proper percent-encoding (e.g., spaces become
%20). - Review dynamically generated URLs: If URLs are built through string concatenation or template logic, double-check that all parts are joined correctly.
Examples
Incorrect: Missing a slash after the scheme
<ahref="https:/example.com">Visit Example</a>
The validator reports this because https:/example.com has only one slash after the colon instead of the required two.
Incorrect: Missing both slashes
<ahref="https:example.com">Visit Example</a>
This is also invalid — the colon must be followed by // for https URLs.
Correct: Properly formatted URL
<ahref="https://example.com">Visit Example</a>
Correct: URL with a path
<ahref="https://example.com/blog/my-post">Read the Post</a>
Correct: URL with encoded spaces
<ahref="https://example.com/search?q=hello%20world">Search</a>
Incorrect: Unencoded space in URL
<ahref="https://example.com/my page">My Page</a>
Spaces are not valid in URLs. Use %20 or + (in query strings) instead:
<ahref="https://example.com/my%20page">My Page</a>
URLs follow a strict syntax defined by RFC 3986 and the URL Living Standard. Only a specific set of characters are allowed to appear unencoded in a URL. Curly braces ({ and }) are among the characters that fall outside this permitted set. When the W3C validator encounters a raw { or } in an href value, it reports the error: Bad value for attribute "href" on element "a": Illegal character in fragment.
This issue commonly arises in a few scenarios:
- Server-side or client-side template placeholders left unresolved in the rendered HTML (e.g.,
{id},{{slug}}). - URLs copied from API documentation that use curly braces to indicate variable segments (e.g.,
/users/{userId}/posts). - Malformed or auto-generated URLs where curly braces were included by mistake.
Why This Matters
Standards compliance: The HTML specification requires that href values conform to valid URL syntax. Curly braces violate this requirement, producing invalid HTML.
Browser inconsistency: While most modern browsers will attempt to handle URLs with illegal characters by silently encoding them, this behavior is not guaranteed across all browsers or versions. Relying on browser error correction can lead to unpredictable results.
Accessibility and interoperability: Assistive technologies, web crawlers, and other tools that parse HTML may not handle illegal URL characters gracefully. Invalid URLs can break link extraction, bookmarking, and sharing functionality.
Debugging difficulty: If curly braces appear in your rendered HTML, it often signals that a template variable was not properly resolved, which may point to a deeper bug in your application logic.
How to Fix It
The fix depends on why the curly braces are there:
If the curly braces are literal characters that should be part of the URL, replace them with their percent-encoded equivalents:
{becomes%7Band}becomes%7D.If the curly braces are template placeholders (e.g.,
{userId}), ensure your server-side or client-side code resolves them to actual values before the HTML is sent to the browser. The rendered HTML should never contain unresolved template variables.If the curly braces were included by mistake, simply remove them.
Examples
Incorrect: Raw curly braces in href
<ahref="https://example.com/api/users/{userId}/profile">View Profile</a>
This triggers the validation error because { and } are illegal URL characters.
Correct: Percent-encoded curly braces
If the curly braces are meant to be literal parts of the URL:
<ahref="https://example.com/api/users/%7BuserId%7D/profile">View Profile</a>
Correct: Resolved template variable
If the curly braces were template placeholders, ensure your templating engine resolves them before rendering. The final HTML should look like:
<ahref="https://example.com/api/users/42/profile">View Profile</a>
Incorrect: Curly braces in a fragment identifier
<ahref="https://example.com/docs#section-{name}">Jump to Section</a>
Correct: Percent-encoded fragment
<ahref="https://example.com/docs#section-%7Bname%7D">Jump to Section</a>
Incorrect: Curly braces in query parameters
<ahref="https://example.com/search?filter={active}">Active Items</a>
Correct: Percent-encoded query parameter
<ahref="https://example.com/search?filter=%7Bactive%7D">Active Items</a>
Using JavaScript for dynamic URLs
If you need to build URLs dynamically with values that might contain special characters, use encodeURIComponent() in JavaScript rather than inserting raw values into href attributes:
<aid="dynamic-link"href="https://example.com">Dynamic Link</a>
<script>
varvalue="{some-value}";
varlink=document.getElementById("dynamic-link");
link.href="https://example.com/path?param="+encodeURIComponent(value);
</script>
This ensures that any special characters, including curly braces, are automatically percent-encoded in the resulting URL.
A backslash (\) is not a valid character in a URL fragment and must be replaced with a forward slash (/) or percent-encoded.
The href attribute on an <a> element must contain a valid URL according to the URL Living Standard. The fragment portion of a URL — the part after the # symbol — follows the same rule: it can contain most characters, but the backslash (\) is explicitly forbidden as a bare character.
This commonly happens when copying file paths from Windows, which uses backslashes as directory separators, and pasting them into an href. Browsers may silently convert \ to /, but the markup is still invalid.
To fix this, replace every \ with / in the URL. If for some reason you actually need a literal backslash in the fragment, percent-encode it as %5C.
HTML Examples
❌ Invalid: backslash in the fragment
<ahref="page.html#section\one">Link</a>
✅ Fixed: use a forward slash or percent-encoding
<!-- Option 1: Replace with forward slash -->
<ahref="page.html#section/one">Link</a>
<!-- Option 2: Percent-encode the backslash -->
<ahref="page.html#section%5Cone">Link</a>
The URL standard (defined by WHATWG) specifies a strict set of characters allowed in each part of a URL. A space character is not among them. When the validator encounters a literal space in an href value, it reports the error "Illegal character in scheme data: space is not allowed." This applies to spaces anywhere in the URL — the path, query string, fragment, or even after the scheme (e.g., https:).
While most modern browsers are forgiving and will attempt to fix malformed URLs by encoding spaces automatically, relying on this behavior is problematic for several reasons:
- Standards compliance: Invalid URLs violate the HTML specification, and markup that depends on browser error-correction is fragile and unpredictable.
- Accessibility: Assistive technologies, such as screen readers, may not handle malformed URLs the same way browsers do. This can result in broken links for users relying on these tools.
- Interoperability: Non-browser consumers of your HTML — search engine crawlers, link checkers, email clients, RSS readers, and APIs — may not perform the same auto-correction, leading to broken links or missed content.
- Copy-paste and sharing: When users copy a malformed URL from the source, the space can cause the link to break when pasted into other applications.
How to fix it
The fix depends on where the space appears:
- In the path or fragment: Replace each space with
%20. For example,/my file.htmlbecomes/my%20file.html. - In the query string: You can use
%20or, if the value is part ofapplication/x-www-form-urlencodeddata,+is also acceptable for spaces within query parameter values. However,%20is universally safe. - Programmatically: Use
encodeURI()in JavaScript to encode a full URL (it preserves structural characters like/,?, and#). UseencodeURIComponent()to encode individual query parameter values. On the server side, use your language's equivalent URL-encoding function.
If you're writing URLs by hand in HTML, simply find every space and replace it with %20. If URLs are generated dynamically (from a database, CMS, or user input), ensure your templating or server-side code encodes them before inserting into the markup.
Examples
Invalid — space in the path
<ahref="https://example.com/docs/My Report.pdf">Download Report</a>
The literal space between "My" and "Report" triggers the validator error.
Fixed — space encoded as %20
<ahref="https://example.com/docs/My%20Report.pdf">Download Report</a>
Invalid — space in a query parameter
<ahref="https://example.com/search?q=hello world">Search</a>
Fixed — space encoded in the query string
<ahref="https://example.com/search?q=hello%20world">Search</a>
Invalid — multiple spaces in different URL parts
<ahref="https://example.com/my folder/page two.html?ref=some value#my section">Link</a>
Fixed — all spaces encoded
<ahref="https://example.com/my%20folder/page%20two.html?ref=some%20value#my%20section">Link</a>
Encoding URLs with JavaScript
If you're building URLs dynamically, use the built-in encoding functions rather than doing manual string replacement:
<script>
// encodeURI encodes a full URL but preserves :, /, ?, #, etc.
consturl=encodeURI("https://example.com/docs/My Report.pdf");
// Result: "https://example.com/docs/My%20Report.pdf"
// encodeURIComponent encodes a single value (for query params)
constquery=encodeURIComponent("hello world");
// Result: "hello%20world"
</script>
Note that encodeURI() is appropriate for encoding a complete URL, while encodeURIComponent() should be used for individual components like query parameter values — it encodes characters such as / and ? that have structural meaning in a URL.
In the structure of a URL, the @ symbol has a special meaning: it separates the userinfo component (username and password) from the host. A URL with credentials follows this pattern:
scheme://username:password@hostname/path
When the username or password itself contains an @ character — for example, an email address used as a username — the browser or URL parser may not be able to determine where the credentials end and the hostname begins. For instance, in http://user@name:pass@example.com, it's unclear whether the host is name or example.com.
The URL Standard (maintained by WHATWG) requires that any @ appearing within the userinfo component be percent-encoded as %40. Percent-encoding replaces the literal character with a % followed by its hexadecimal ASCII code (40 for @). This removes the ambiguity and ensures all parsers interpret the URL identically.
While modern browsers may attempt to handle ambiguous URLs, the behavior is not guaranteed to be consistent across all user agents, link checkers, or HTTP clients. Properly encoding these characters ensures reliable behavior everywhere and keeps your HTML valid.
Note: Including credentials directly in URLs is generally discouraged for security reasons, as they may be exposed in browser history, server logs, and referrer headers. Consider alternative authentication methods when possible.
Examples
❌ Incorrect: unencoded @ in the username
<ahref="http://user@name:password@example.com/path">Login</a>
Here, the parser cannot reliably distinguish user@name as the username from the @ that separates credentials from the host.
✅ Correct: percent-encoded @ in the username
<ahref="http://user%40name:password@example.com/path">Login</a>
The @ within the username is encoded as %40, leaving only one literal @ to serve as the delimiter before the hostname.
❌ Incorrect: unencoded @ in the password
<ahref="http://admin:p@ss@example.com/dashboard">Dashboard</a>
✅ Correct: percent-encoded @ in the password
<ahref="http://admin:p%40ss@example.com/dashboard">Dashboard</a>
❌ Incorrect: email address used as username without encoding
<ahref="ftp://joe@example.org:secret@ftp.example.com/files">Files</a>
✅ Correct: email address with @ percent-encoded
<ahref="ftp://joe%40example.org:secret@ftp.example.com/files">Files</a>
To fix this issue, identify every @ character that appears before the final @ in the authority section of the URL and replace it with %40. The last @ in the authority is the actual delimiter and must remain as a literal character.
The <area> element defines a clickable region within an <map> element, which is used with images to create image maps. When an <area> element includes an href attribute, the browser treats it as a hyperlink. The value of href must be a valid URL, and http:// alone fails validation because the URL specification requires a host after the :// separator. An empty host is not permitted.
This matters for several reasons. Browsers may handle malformed URLs unpredictably — some might ignore the link, others might attempt navigation to a nonsensical destination, and others might throw a network error. Screen readers and other assistive technologies rely on valid href values to announce links correctly and provide meaningful navigation to users. From a standards compliance perspective, the WHATWG URL Standard explicitly rejects URLs with empty hosts, and the W3C validator flags this as an error.
This issue commonly appears when placeholder URLs are left in during development, when CMS tools generate incomplete markup, or when a URL value is dynamically constructed but the host portion ends up empty.
How to fix it
- If the area should link somewhere, replace
http://with the full, intended URL including the host (e.g.,https://example.com/page). - If the area is a temporary placeholder, use
href="#"to create a valid no-op link, though be aware this will scroll the page to the top when clicked. - If the area shouldn't be interactive, remove the
hrefattribute entirely. Withouthref, the<area>element is not a hyperlink and won't be clickable.
Examples
Invalid: empty host in URL
The value http:// has no host, which triggers the validation error.
<imgsrc="diagram.png"alt="Site map"usemap="#siteMap">
<mapname="siteMap">
<areashape="rect"coords="30,23,183,191"href="http://"alt="Home page">
</map>
Fixed: complete URL with a valid host
Providing a full URL with a host resolves the error.
<imgsrc="diagram.png"alt="Site map"usemap="#siteMap">
<mapname="siteMap">
<areashape="rect"coords="30,23,183,191"href="https://example.com/"alt="Home page">
</map>
Fixed: placeholder link with #
If you need the area to remain clickable but don't have a destination yet, href="#" is a valid placeholder.
<imgsrc="diagram.png"alt="Site map"usemap="#siteMap">
<mapname="siteMap">
<areashape="rect"coords="30,23,183,191"href="#"alt="Home page">
</map>
Fixed: non-interactive area without href
If the area doesn't need to be a link, simply omit the href attribute.
<imgsrc="diagram.png"alt="Site map"usemap="#siteMap">
<mapname="siteMap">
<areashape="rect"coords="30,23,183,191"alt="Home page">
</map>
The <pattern> element lives inside <svg>, and SVG is an XML-based language. Unlike regular HTML — where id values follow relatively relaxed rules — SVG content must comply with XML 1.0 naming conventions. This means id values have stricter character and formatting requirements than you might be used to in plain HTML.
XML 1.0 Name Rules
An XML 1.0 name (used for id attributes in SVG) must follow these rules:
- First character must be a letter (
A–Z,a–z) or an underscore (_). - Subsequent characters can be letters, digits (
0–9), hyphens (-), underscores (_), or periods (.). - Spaces and special characters like
!,@,#,$,%,(,), etc. are not allowed anywhere in the name.
Common mistakes that trigger this error include starting an id with a digit (e.g., 1pattern), a hyphen (e.g., -myPattern), or a period (e.g., .dotPattern), or including characters like spaces or colons.
Why This Matters
- Standards compliance: SVG is parsed as XML in many contexts. An invalid XML name can cause parsing errors or unexpected behavior, especially when SVG is served with an XML MIME type or embedded in XHTML.
- Functionality: The
<pattern>element'sidis typically referenced viaurl(#id)infillorstrokeattributes. An invalididmay cause the pattern reference to silently fail, leaving elements unfilled or invisible. - Cross-browser consistency: While some browsers are lenient with invalid XML names, others are not. Using valid names ensures consistent rendering across all browsers and environments.
How to Fix
Rename the id value so it starts with a letter or underscore and contains only valid characters. If you reference this id elsewhere (e.g., in fill="url(#...)" or in CSS), update those references to match.
Examples
❌ Invalid: id starts with a digit
<svgwidth="200"height="200"xmlns="http://www.w3.org/2000/svg">
<defs>
<patternid="1stPattern"width="10"height="10"patternUnits="userSpaceOnUse">
<circlecx="5"cy="5"r="3"fill="blue"/>
</pattern>
</defs>
<rectwidth="200"height="200"fill="url(#1stPattern)"/>
</svg>
❌ Invalid: id starts with a hyphen
<svgwidth="200"height="200"xmlns="http://www.w3.org/2000/svg">
<defs>
<patternid="-stripe-bg"width="10"height="10"patternUnits="userSpaceOnUse">
<rectwidth="5"height="10"fill="red"/>
</pattern>
</defs>
<rectwidth="200"height="200"fill="url(#-stripe-bg)"/>
</svg>
❌ Invalid: id contains special characters
<svgwidth="200"height="200"xmlns="http://www.w3.org/2000/svg">
<defs>
<patternid="my pattern!"width="10"height="10"patternUnits="userSpaceOnUse">
<circlecx="5"cy="5"r="3"fill="green"/>
</pattern>
</defs>
<rectwidth="200"height="200"fill="url(#my pattern!)"/>
</svg>
✅ Valid: id starts with a letter
<svgwidth="200"height="200"xmlns="http://www.w3.org/2000/svg">
<defs>
<patternid="firstPattern"width="10"height="10"patternUnits="userSpaceOnUse">
<circlecx="5"cy="5"r="3"fill="blue"/>
</pattern>
</defs>
<rectwidth="200"height="200"fill="url(#firstPattern)"/>
</svg>
✅ Valid: id starts with an underscore
<svgwidth="200"height="200"xmlns="http://www.w3.org/2000/svg">
<defs>
<patternid="_stripe-bg"width="10"height="10"patternUnits="userSpaceOnUse">
<rectwidth="5"height="10"fill="red"/>
</pattern>
</defs>
<rectwidth="200"height="200"fill="url(#_stripe-bg)"/>
</svg>
✅ Valid: Using letters, digits, hyphens, and underscores
<svgwidth="200"height="200"xmlns="http://www.w3.org/2000/svg">
<defs>
<patternid="dot-grid_v2"width="10"height="10"patternUnits="userSpaceOnUse">
<circlecx="5"cy="5"r="3"fill="green"/>
</pattern>
</defs>
<rectwidth="200"height="200"fill="url(#dot-grid_v2)"/>
</svg>
Note that this same XML 1.0 naming rule applies to id attributes on all SVG elements — not just <pattern>. If you see similar errors on elements like <linearGradient>, <clipPath>, or <filter>, the same fix applies: ensure the id starts with a letter or underscore and uses only valid characters.
The media attribute on a <link> element specifies the conditions under which the linked resource should apply. It accepts either a simple media type or a full media query. When the validator reports "unrecognized media," it means the value you provided doesn't match any known media type or valid media query syntax.
Several older media types that were defined in earlier CSS specifications have been deprecated. Types like handheld, projection, tv, tty, aural, braille, and embossed are no longer recognized as valid. Modern CSS and HTML only support three media types: all, screen, and print. If you're using a deprecated type, you should replace it with an appropriate modern media query that targets the device characteristics you need.
Beyond deprecated types, this error also occurs when a media query expression is malformed — for example, missing parentheses around a feature expression, using an unknown feature name, or having a typo in the value.
Why this matters
- Standards compliance: Using unrecognized media types means your HTML doesn't conform to the current HTML and CSS specifications.
- Browser behavior: Browsers may ignore the entire
<link>element or apply the resource unconditionally when they encounter an unrecognized media type, leading to unexpected results. - Performance: The
mediaattribute helps browsers prioritize resource loading. A valid media query allows the browser to defer loading stylesheets that don't match the current context (e.g., print stylesheets), improving page load performance.
How to fix it
- Replace deprecated media types with
screen,print, orall, or use modern media queries that target specific device features. - Check for typos in your media type or query expression.
- Validate your media query syntax — feature expressions must be wrapped in parentheses and use recognized feature names like
max-width,orientation, orprefers-color-scheme.
Examples
Incorrect: using a deprecated media type
<linkrel="stylesheet"href="mobile.css"media="handheld">
The handheld media type is deprecated and will trigger the validation error.
Incorrect: misspelled media type
<linkrel="stylesheet"href="styles.css"media="screen">
Incorrect: malformed media query
<linkrel="stylesheet"href="responsive.css"media="max-width: 768px">
The feature expression is missing its surrounding parentheses.
Correct: using valid media types
<linkrel="stylesheet"href="general.css">
<linkrel="stylesheet"href="print.css"media="print">
<linkrel="stylesheet"href="screen.css"media="screen">
When no media attribute is specified, it defaults to all.
Correct: replacing deprecated types with modern media queries
Instead of media="handheld", use a media query that targets small screens or specific device capabilities:
<linkrel="stylesheet"href="mobile.css"media="screen and (max-width: 768px)">
Correct: using complex media queries
<linkrel="stylesheet"href="dark.css"media="(prefers-color-scheme: dark)">
<linkrel="stylesheet"href="portrait.css"media="screen and (orientation: portrait)">
<linkrel="stylesheet"href="large.css"media="screen and (min-width: 1200px)">
Valid media types reference
| Media type | Description |
|---|---|
all | Matches all devices (default when omitted) |
print | Matches printers and print preview mode |
screen | Matches screens (computers, tablets, phones) |
For anything more specific than these three types, use media feature expressions like (max-width: 600px), (hover: hover), or (prefers-reduced-motion: reduce) to target the exact device characteristics you need.
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