HTML Guides
Learn how to identify and fix common HTML validation errors flagged by the W3C Validator — so your pages are standards-compliant and render correctly across every browser. Also check our Accessibility Guides.
In HTML, boolean attributes work differently than you might expect from programming languages. A boolean attribute’s presence on an element represents the true value, and its absence represents false. You don’t set them to "true" or "false" like you would in JavaScript or other languages. According to the WHATWG HTML specification, a boolean attribute has exactly three valid forms:
- The attribute name alone (e.g., multiple)
- The attribute with an empty value (e.g., multiple="")
- The attribute with a value matching its own name, case-insensitively (e.g., multiple="multiple")
Setting multiple="true" is invalid because "true" is not one of the permitted values. While browsers are forgiving and will typically still treat the attribute as present (effectively enabling it), this produces a W3C validation error and does not conform to the HTML standard. Relying on browser leniency leads to inconsistent behavior, makes your code harder to maintain, and can cause problems with HTML processing tools or strict parsers.
This same rule applies to all boolean attributes in HTML, including disabled, readonly, checked, required, hidden, autoplay, and many others.
It’s also important to note that multiple="false" does not disable the attribute. Because the attribute is still present on the element, the browser treats it as enabled. To disable a boolean attribute, you must remove it from the element entirely.
Examples
❌ Invalid: using "true" as the value
<label for="colors">Select your favorite colors:</label>
<select id="colors" name="colors" multiple="true">
<option value="red">Red</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
</select>
This triggers the validation error: Bad value “true” for attribute “multiple” on element “select”.
✅ Fixed: attribute name only (preferred)
<label for="colors">Select your favorite colors:</label>
<select id="colors" name="colors" multiple>
<option value="red">Red</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
</select>
✅ Fixed: empty string value
<label for="colors">Select your favorite colors:</label>
<select id="colors" name="colors" multiple="">
<option value="red">Red</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
</select>
✅ Fixed: value matching the attribute name
<label for="colors">Select your favorite colors:</label>
<select id="colors" name="colors" multiple="multiple">
<option value="red">Red</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
</select>
❌ Common mistake: using "false" to disable
<!-- This does NOT disable multiple selection — the attribute is still present -->
<select name="colors" multiple="false">
<option value="red">Red</option>
<option value="green">Green</option>
</select>
✅ Correct way to disable: remove the attribute entirely
<label for="color">Select a color:</label>
<select id="color" name="color">
<option value="red">Red</option>
<option value="green">Green</option>
</select>
In HTML, boolean attributes like selected work differently than you might expect if you’re coming from a programming language. A boolean attribute’s presence alone means “true,” and its absence means “false.” Setting selected="true" is invalid because the only permitted values for a boolean attribute are the empty string ("") or the attribute’s own name (e.g., selected="selected"). The value "true" is not recognized by the HTML specification, which is why the W3C validator flags it.
This matters for several reasons. First, it violates the WHATWG HTML specification, which explicitly defines how boolean attributes must be written. Second, while most browsers are forgiving and will still treat selected="true" as if the option is selected, relying on this lenient behavior is risky — it can lead to inconsistencies across browsers or tools that parse HTML strictly. Third, invalid markup can cause problems for assistive technologies, automated testing tools, and server-side HTML processors that follow the spec closely.
The same rule applies to other boolean attributes like disabled, checked, readonly, multiple, required, and hidden. None of them should be set to "true" or "false".
It’s also worth noting that setting a boolean attribute to "false" (e.g., selected="false") does not turn it off — the attribute’s mere presence activates it. To deactivate a boolean attribute, you must remove it entirely from the element.
Examples
❌ Invalid: using selected="true"
<select name="color">
<option selected="true">Red</option>
<option>Green</option>
<option>Blue</option>
</select>
This triggers the validation error because "true" is not a valid value for the boolean selected attribute.
✅ Valid: bare attribute (preferred)
<select name="color">
<option selected>Red</option>
<option>Green</option>
<option>Blue</option>
</select>
The simplest and most common way to write a boolean attribute — just include the attribute name with no value.
✅ Valid: empty string value
<select name="color">
<option selected="">Red</option>
<option>Green</option>
<option>Blue</option>
</select>
An empty string is a valid value for any boolean attribute per the HTML spec.
✅ Valid: attribute name as value
<select name="color">
<option selected="selected">Red</option>
<option>Green</option>
<option>Blue</option>
</select>
Using the attribute’s own name as its value is also valid. This form is sometimes seen in XHTML-style markup and in templating systems.
❌ Invalid: using selected="false" to deselect
<select name="color">
<option selected="false">Red</option>
<option>Green</option>
<option>Blue</option>
</select>
This is both invalid and misleading. The option will still be selected because the selected attribute is present. To not select an option, simply omit the attribute:
<select name="color">
<option>Red</option>
<option>Green</option>
<option>Blue</option>
</select>
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 using wrap="hard", you must also specify the cols attribute 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" with wrap="soft" for an explicit equivalent.
- Remove the wrap attribute entirely if you want the default soft wrapping behavior.
- Use wrap="hard" with a cols attribute if you actually need hard line breaks inserted on submission.
Examples
❌ Invalid: using the non-standard "virtual" value
<form>
<label for="msg">Message</label>
<textarea id="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>
<label for="msg">Message</label>
<textarea id="msg" name="msg" wrap="soft"></textarea>
</form>
✅ Fixed: omitting wrap entirely (defaults to "soft")
<form>
<label for="msg">Message</label>
<textarea id="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>
<label for="msg">Message</label>
<textarea id="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.
When you write an attribute like maxlength="200 and accidentally omit the closing quote, everything that follows — including subsequent attribute names and their values — gets absorbed into that one attribute’s value. In this case, the validator sees the value of maxlength as 200 aria-required= (or similar), which is not a valid integer. The parser doesn’t encounter a closing " until it finds the next quotation mark further along in the tag, causing a cascade of errors.
This is a problem for several reasons:
- Broken functionality: The maxlength attribute won’t work because 200 aria-required= is not a valid number. The browser cannot determine the intended character limit.
- Lost attributes: The aria-required attribute is swallowed into the malformed maxlength value, so it never gets applied as a separate attribute. Assistive technologies like screen readers won’t know the field is required.
- Accessibility impact: Since aria-required="true" is lost, users who rely on screen readers won’t receive the information that the field is mandatory, potentially leading to form submission errors and a frustrating experience.
The root cause is almost always a missing closing quotation mark. Carefully check that every attribute value has both an opening and a closing ". This kind of typo is easy to make and easy to miss, especially in long tags with many attributes.
Examples
Incorrect — missing closing quote on maxlength
The closing " after 200 is missing, so the value of maxlength extends all the way to the next quotation mark it finds:
<input type="text" name="nome" id="nome" maxlength="200 aria-required="true">
The validator interprets maxlength as having the value 200 aria-required=, and only true ends up as the value of an unintended or malformed attribute. Nothing works as expected.
Correct — properly quoted attributes
Each attribute has its own properly matched pair of quotation marks:
<input type="text" name="nome" id="nome" maxlength="200" aria-required="true">
Here, maxlength="200" correctly limits the input to 200 characters, and aria-required="true" is a separate attribute that tells assistive technologies the field is required.
Incorrect — missing closing quote with more attributes
This issue can happen with any combination of attributes. Here, a missing quote after maxlength absorbs class and placeholder:
<input type="text" maxlength="50 class="username" placeholder="Enter name">
Correct — all quotes properly closed
<input type="text" maxlength="50" class="username" placeholder="Enter name">
Tips for avoiding this issue
- Use a code editor with syntax highlighting. Most editors color attribute values differently from attribute names. If you see an attribute name rendered in the same color as a value string, a quote is likely missing.
- Format attributes one per line on complex elements. This makes it much easier to spot mismatched quotes:
<input
type="text"
name="nome"
id="nome"
maxlength="200"
aria-required="true">
- Validate early and often. Running your HTML through the W3C validator regularly helps catch these small typos before they cause confusing bugs in production.
The device-width and device-height media features (along with their min- and max- prefixed variants) originally referred to the physical dimensions of the device’s entire screen, regardless of how much space was actually available to the document. In practice, this distinction caused confusion and inconsistent behavior across browsers. On most modern devices and browsers, device-width and width return the same value anyway, making the device-* variants redundant.
The device-* media features were frequently used as a proxy to detect mobile devices, but this was never a reliable approach. A narrow browser window on a desktop monitor would not trigger a max-device-width query even though the available layout space was small. Conversely, modern phones and tablets with high-resolution screens can report large device widths that don’t reflect the actual CSS viewport size. The viewport-based alternatives (width, height, aspect-ratio) more accurately represent the space available for rendering your content.
Using deprecated media features causes W3C validation warnings and may eventually lose browser support entirely. Replacing them ensures your stylesheets are future-proof, standards-compliant, and behave consistently across all devices and window sizes.
How to fix it
Replace any device-width, device-height, or device-aspect-ratio media feature (including min- and max- prefixed versions) with the corresponding viewport-based equivalent:
| Deprecated feature | Replacement |
|---|---|
| device-width | width |
| min-device-width | min-width |
| max-device-width | max-width |
| device-height | height |
| min-device-height | min-height |
| max-device-height | max-height |
| device-aspect-ratio | aspect-ratio |
The width media feature describes the width of the viewport (the targeted display area of the output device), including the size of a rendered scroll bar if any. This is the value you almost always want when writing responsive styles.
Examples
Incorrect: using deprecated max-device-width
This triggers the validation error because max-device-width is deprecated:
<link rel="stylesheet" media="only screen and (max-device-width: 768px)" href="mobile.css">
Correct: using max-width instead
Replace max-device-width with max-width to query the viewport width:
<link rel="stylesheet" media="only screen and (max-width: 768px)" href="mobile.css">
Incorrect: using deprecated min-device-width in a range
<link rel="stylesheet" media="screen and (min-device-width: 768px) and (max-device-width: 1024px)" href="tablet.css">
Correct: using viewport-based equivalents
<link rel="stylesheet" media="screen and (min-width: 768px) and (max-width: 1024px)" href="tablet.css">
Incorrect: using deprecated device-aspect-ratio
<link rel="stylesheet" media="screen and (device-aspect-ratio: 16/9)" href="widescreen.css">
Correct: using aspect-ratio
<link rel="stylesheet" media="screen and (aspect-ratio: 16/9)" href="widescreen.css">
Applying the same fix in CSS @media rules
The same deprecation applies to @media rules inside stylesheets. While the W3C validator specifically flags the media attribute on <link> elements, you should update your CSS as well:
/* Deprecated */
@media only screen and (max-device-width: 768px) {
.sidebar { display: none; }
}
/* Correct */
@media only screen and (max-width: 768px) {
.sidebar { display: none; }
}
If your site relies on a <meta name="viewport"> tag (as most responsive sites do), the viewport width already matches the device’s CSS pixel width, so switching from device-width to width will produce identical results in virtually all cases.
The device-width, device-height, and device-aspect-ratio media features (including their min- and max- prefixed variants) refer to the physical dimensions of the entire screen, not the available space where your content is actually rendered. This distinction matters because the viewport size — the area your page occupies — can differ significantly from the full device screen size. For example, a browser window might not be maximized, or browser chrome might consume screen real estate. Using device-based queries means your responsive breakpoints may not match what users actually see.
These features were also commonly used as a proxy for detecting mobile devices, but this approach is unreliable. A small-screened laptop and a large tablet can have similar device widths, making device-based detection a poor heuristic. The W3C deprecated these features and recommends using viewport-based alternatives that more accurately reflect the rendering context of your document.
Beyond standards compliance, using deprecated media features can cause issues with future browser support. While current browsers still recognize them, there is no guarantee they will continue to do so. Replacing them now ensures your stylesheets remain functional and forward-compatible.
How to fix it
Replace each deprecated device-* media feature with its viewport-based equivalent:
| Deprecated feature | Replacement |
|---|---|
| device-width | width |
| min-device-width | min-width |
| max-device-width | max-width |
| device-height | height |
| min-device-height | min-height |
| max-device-height | max-height |
| device-aspect-ratio | aspect-ratio |
| min-device-aspect-ratio | min-aspect-ratio |
| max-device-aspect-ratio | max-aspect-ratio |
The width media feature describes the width of the targeted display area of the output device. For continuous media, this is the width of the viewport including the size of a rendered scroll bar (if any). This is almost always the value you actually want when building responsive layouts.
Examples
Incorrect: using deprecated min-device-width
This triggers the validation error because min-device-width is deprecated:
<link rel="stylesheet" media="only screen and (min-device-width: 768px)" href="tablet.css">
Correct: using min-width instead
Replace min-device-width with min-width to query the viewport width:
<link rel="stylesheet" media="only screen and (min-width: 768px)" href="tablet.css">
Incorrect: using max-device-width for a mobile breakpoint
<link rel="stylesheet" media="screen and (max-device-width: 480px)" href="mobile.css">
Correct: using max-width
<link rel="stylesheet" media="screen and (max-width: 480px)" href="mobile.css">
Incorrect: using device-aspect-ratio
<link rel="stylesheet" media="screen and (device-aspect-ratio: 16/9)" href="widescreen.css">
Correct: using aspect-ratio
<link rel="stylesheet" media="screen and (aspect-ratio: 16/9)" href="widescreen.css">
Incorrect: combining multiple deprecated features
<link rel="stylesheet" media="screen and (min-device-width: 768px) and (max-device-width: 1024px)" href="tablet.css">
Correct: using viewport-based equivalents
<link rel="stylesheet" media="screen and (min-width: 768px) and (max-width: 1024px)" href="tablet.css">
The same replacements apply when these deprecated features appear in CSS @media rules or in the media attribute on <style> and <source> elements. Updating them across your entire codebase ensures consistent, standards-compliant responsive behavior.
The accept attribute provides browsers with a hint about which file types the user should be able to select through the file picker dialog. While browsers may still allow users to select other file types, the attribute helps filter the file picker to show relevant files first, improving the user experience.
The W3C validator reports this error when it encounters tokens in the accept attribute that don’t conform to the expected format. The attribute value is parsed as a set of comma-separated tokens, and each token must be one of the following:
- A valid MIME type such as application/pdf, text/plain, or image/png (the / character separating type and subtype is required).
- A file extension starting with a period, such as .pdf, .docx, or .jpg.
- One of three wildcard MIME types: audio/*, video/*, or image/*.
Common mistakes that trigger this error include using bare file extensions without the leading dot (e.g., pdf instead of .pdf), using arbitrary words that aren’t valid MIME types, or including spaces in a way that creates malformed tokens. The HTML specification (WHATWG) defines strict rules for how these tokens are parsed, and violating them produces a validation error.
Getting this attribute right matters for several reasons. First, a correctly specified accept attribute helps users by pre-filtering the file picker, so they don’t have to hunt through unrelated files. Second, assistive technologies may use this attribute to communicate accepted file types to users. Third, some browsers may silently ignore a malformed accept value entirely, removing the helpful filtering behavior you intended.
To fix the issue, review each token in your accept attribute and ensure it matches one of the three valid formats listed above. If you’re unsure of the correct MIME type for a file format, consult the IANA Media Types registry. When in doubt, dot-prefixed file extensions (like .pdf or .docx) are often simpler and more readable.
Examples
Incorrect: bare words without dots or MIME type format
<input type="file" name="document" accept="doc, docx, pdf">
The tokens doc, docx, and pdf are neither valid MIME types (no /) nor valid file extensions (no leading .), so the validator rejects them.
Incorrect: spaces creating malformed tokens
<input type="file" name="photo" accept="image/png, image/jpeg">
While most browsers handle spaces after commas gracefully, the validator may flag this depending on parsing. It’s safest to avoid spaces or keep them minimal.
Correct: using dot-prefixed file extensions
<input type="file" name="document" accept=".doc,.docx,.pdf">
Correct: using valid MIME types
<input type="file" name="document" accept="application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/pdf">
Correct: mixing MIME types, extensions, and wildcards
<input type="file" name="media" accept="image/*,.pdf,video/mp4">
This accepts all image types, PDF files, and MP4 videos. Mixing formats is perfectly valid as long as each token individually conforms to the specification.
Correct: using wildcard MIME types for broad categories
<input type="file" name="photo" accept="image/*">
This allows the user to select any image file, regardless of specific format.
The aria-current attribute indicates that an element represents the current item within a container or set of related elements. It is designed to convey to assistive technology users what sighted users already perceive through visual styling — for example, a highlighted link in a navigation bar or the active step in a multi-step wizard.
Because assistive technologies rely on a known set of token values to communicate meaning, using an invalid value means the attribute is effectively meaningless to screen readers and other tools. This undermines accessibility for the users who need it most. Browsers and assistive technologies will not interpret an unrecognized value as true; they may ignore the attribute or treat it as false, leading to a confusing experience.
Common mistakes that trigger this error include:
- Using an empty string: aria-current=""
- Using a custom or misspelled value: aria-current="active", aria-current="yes", aria-current="pgae"
- Dynamically setting the attribute to undefined or null as a string
Accepted values
Each accepted value carries a specific semantic meaning. Use the most descriptive one that matches your context:
- page — The current page within a set of pages (e.g., the active link in a breadcrumb or navigation menu).
- step — The current step within a process (e.g., the active step in a checkout flow).
- location — The current location within an environment or context (e.g., the highlighted node in a flow chart).
- date — The current date within a collection of dates (e.g., today’s date in a calendar).
- time — The current time within a set of times (e.g., the current time slot in a timetable).
- true — A generic indication that the element is the current item, when none of the more specific values apply.
- false — Explicitly indicates the element is not the current item. This is equivalent to omitting the attribute entirely.
Examples
❌ Invalid: empty string
An empty string is not a valid value for aria-current:
<nav>
<ol>
<li><a href="/step-1" aria-current="">Step 1</a></li>
<li><a href="/step-2">Step 2</a></li>
</ol>
</nav>
❌ Invalid: custom keyword
Values like "active" or "yes" are not recognized:
<nav>
<ul>
<li><a href="/" aria-current="active">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
✅ Fixed: using page for navigation
When marking the current page in a navigation menu, page is the most appropriate value:
<nav>
<ul>
<li><a href="/" aria-current="page">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
✅ Fixed: using step for a multi-step process
<ol>
<li><a href="/checkout/cart">Cart</a></li>
<li><a href="/checkout/shipping" aria-current="step">Shipping</a></li>
<li><a href="/checkout/payment">Payment</a></li>
</ol>
✅ Fixed: using true as a generic fallback
When none of the specific token values fit, use true:
<ul>
<li><a href="/item-1" aria-current="true">Item 1</a></li>
<li><a href="/item-2">Item 2</a></li>
<li><a href="/item-3">Item 3</a></li>
</ul>
✅ Fixed: removing the attribute instead of setting false
If an element is not the current item, simply omit aria-current rather than setting it to false or an empty string. Both of the following are valid, but omitting the attribute is cleaner:
<!-- Valid but unnecessary -->
<a href="/about" aria-current="false">About</a>
<!-- Preferred: just omit it -->
<a href="/about">About</a>
Tips for dynamic frameworks
If you’re using a JavaScript framework that conditionally sets aria-current, make sure the attribute is either set to a valid value or removed from the DOM entirely. Avoid patterns that render aria-current="" or aria-current="undefined" when the element is not current. In React, for instance, you can pass undefined (not the string "undefined") to omit the attribute:
<!-- What your framework should render for the current page -->
<a href="/" aria-current="page">Home</a>
<!-- What it should render for non-current pages (no attribute at all) -->
<a href="/about">About</a>
The aria-required attribute is a WAI-ARIA property that signals to assistive technologies (like screen readers) that a user must provide a value for a form control before the form can be submitted. According to the ARIA specification, the attribute’s value must be either "true" or "false". Any other value — such as "yes", "1", "", or a misspelling — is invalid and will produce this validation error.
Common mistakes include writing aria-required="yes", aria-required="", aria-required="required", or even aria-required="True" (note: the value is case-sensitive and must be lowercase).
Why this matters
When an invalid value is used, assistive technologies may not correctly interpret whether the field is required. This can lead to a confusing experience for users who rely on screen readers, as they may not be told that a field is mandatory before submitting a form. Using valid attribute values ensures consistent, predictable behavior across all browsers and assistive technologies.
When to use aria-required vs. required
For native HTML form elements like <input>, <select>, and <textarea>, you should use the built-in HTML required attribute. It provides both validation behavior and accessibility information out of the box, without needing ARIA.
The aria-required attribute is intended for custom (non-semantic) form controls — for example, a <div> with a role="textbox" or role="combobox". In these cases, the browser doesn’t know the element is a form control, so aria-required="true" communicates the requirement to assistive technologies.
Examples
❌ Invalid values for aria-required
<!-- "yes" is not a valid value -->
<div
role="textbox"
contenteditable
aria-labelledby="name_label"
aria-required="yes">
</div>
<!-- Empty string is not valid -->
<input type="text" aria-required="">
<!-- "required" is not valid -->
<input type="email" aria-required="required">
✅ Correct usage with aria-required
<div id="email_label">Email Address *</div>
<div
role="textbox"
contenteditable
aria-labelledby="email_label"
aria-required="true"
id="email">
</div>
✅ Explicitly marking a field as not required
<div id="notes_label">Notes (optional)</div>
<div
role="textbox"
contenteditable
aria-labelledby="notes_label"
aria-required="false"
id="notes">
</div>
✅ Preferred approach for native form elements
When using standard HTML form controls, skip aria-required and use the native required attribute instead:
<label for="email">Email Address *</label>
<input type="email" id="email" name="email" required>
<label for="country">Country *</label>
<select id="country" name="country" required>
<option value="">Select a country</option>
<option value="us">United States</option>
<option value="uk">United Kingdom</option>
</select>
The native required attribute automatically conveys the required state to assistive technologies and also triggers built-in browser validation, making it the better choice whenever a native form element is available.
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 -->
<script async="true" src="app.js"></script>
<!-- Bad: "1" is not a valid boolean attribute value -->
<script async="1" src="analytics.js"></script>
<!-- Bad: "yes" is not a valid boolean attribute value -->
<script async="yes" src="tracker.js"></script>
<!-- Bad and misleading: this does NOT disable async -->
<script async="false" src="app.js"></script>
✅ Valid: correct boolean attribute usage
<!-- Preferred: attribute name alone -->
<script async src="app.js"></script>
<!-- Also valid: empty string value -->
<script async="" src="app.js"></script>
<!-- Also valid: value matching attribute name -->
<script async="async" src="app.js"></script>
<!-- Correct way to disable async: remove the attribute -->
<script src="app.js"></script>
✅ Valid: async with module scripts
<script async type="module" src="app.mjs"></script>
<script async type="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.
In HTML, boolean attributes work differently from regular attributes. They don’t take true or false as values. Instead, the mere presence of the attribute represents the “true” state, and its complete absence represents “false.” According to the WHATWG HTML specification, a boolean attribute has exactly three valid representations: the attribute name alone (checked), an empty string value (checked=""), or the attribute name as its own value (checked="checked"). Any other value — including seemingly intuitive ones like "true", "false", "yes", or "no" — is invalid.
This is particularly important to understand because checked="false" does not uncheck the input. Since the attribute is still present, the browser interprets it as checked. This can lead to confusing behavior where a developer writes checked="false" expecting the checkbox to be unchecked, but it renders as checked. To leave an input unchecked, you must omit the checked attribute entirely.
The checked attribute applies to <input> elements of type checkbox and radio. It sets the initial checked state when the page loads. Note that JavaScript uses a different convention — the checked DOM property accepts true or false as JavaScript boolean values — but this does not apply to HTML markup.
Fixing this issue ensures standards compliance, avoids unexpected behavior, and improves code clarity. While browsers are lenient and will typically treat any value as “checked,” relying on this behavior is non-standard and can cause confusion for developers reading the code.
Examples
Invalid: using a string value
<input type="checkbox" checked="true">
<input type="radio" name="color" value="red" checked="yes">
These produce validation errors because "true" and "yes" are not valid boolean attribute values.
Invalid: attempting to set false
<!-- This does NOT uncheck the input — it's still checked! -->
<input type="checkbox" checked="false">
Despite the value "false", the checkbox will still render as checked because the attribute is present.
Valid: attribute name only (recommended)
<input type="checkbox" checked>
<input type="radio" name="color" value="red" checked>
This is the most common and cleanest form.
Valid: empty string value
<input type="checkbox" checked="">
Valid: attribute name as its own value
<input type="checkbox" checked="checked">
This form is sometimes seen in XHTML-compatible markup.
Valid: omitting the attribute to leave unchecked
<input type="checkbox">
To represent an unchecked state, simply leave the attribute off.
Full example in context
<!DOCTYPE html>
<html lang="en">
<head>
<title>Newsletter Preferences</title>
</head>
<body>
<form>
<fieldset>
<legend>Email preferences</legend>
<label>
<input type="checkbox" name="newsletter" checked>
Subscribe to newsletter
</label>
<label>
<input type="checkbox" name="promotions">
Receive promotional emails
</label>
</fieldset>
<fieldset>
<legend>Frequency</legend>
<label>
<input type="radio" name="frequency" value="daily" checked>
Daily
</label>
<label>
<input type="radio" name="frequency" value="weekly">
Weekly
</label>
</fieldset>
</form>
</body>
</html>
In this example, the newsletter checkbox and the “Daily” radio button are pre-selected using the valid checked attribute without any value. The promotions checkbox and “Weekly” radio button are unchecked because the attribute is absent.
The <time> HTML element represents a specific period in time or a duration. Its datetime attribute translates human-readable text into a machine-readable format, enabling browsers, search engines, and assistive technologies to reliably parse and understand temporal data. When the datetime value doesn’t match one of the accepted formats, the machine-readable purpose of the element is defeated — tools cannot interpret the date or time, which undermines features like search engine rich results, calendar integration, and accessibility enhancements for screen readers.
The HTML specification defines several valid formats for the datetime attribute. Here are the most commonly used ones:
| Format | Example | Description |
|---|---|---|
| Date | 2024-03-15 | Year, month, day |
| Month | 2024-03 | Year and month only |
| Year | 2024 | Valid year |
| Yearless date | 03-15 | Month and day without year |
| Time | 14:30 or 14:30:00 | Hours and minutes (seconds optional) |
| Date and time | 2024-03-15T14:30 | Date and time separated by T |
| Date and time with timezone | 2024-03-15T14:30Z or 2024-03-15T14:30+05:30 | With UTC (Z) or offset |
| Duration (precise) | PT1H30M | ISO 8601 duration |
| Duration (approximate) | P2Y6M | Years, months, etc. |
| Week | 2024-W12 | ISO week number |
Common mistakes that trigger this error include:
- Using slashes instead of hyphens: 03/15/2024 instead of 2024-03-15
- Using informal date formats: March 15, 2024 or 15-03-2024
- Omitting the T separator between date and time: 2024-03-15 14:30
- Using 12-hour time with AM/PM: 2:30 PM instead of 14:30
- Providing incomplete values: 2024-3-5 instead of 2024-03-05 (months and days must be zero-padded)
Examples
Invalid: Slash-separated date
<time datetime="03/15/2024">March 15, 2024</time>
Valid: ISO 8601 date format
<time datetime="2024-03-15">March 15, 2024</time>
Invalid: Missing T separator and using AM/PM
<time datetime="2024-03-15 2:30 PM">March 15 at 2:30 PM</time>
Valid: Date-time with T separator and 24-hour time
<time datetime="2024-03-15T14:30">March 15 at 2:30 PM</time>
Invalid: Informal time string
<time datetime="half past two">2:30 PM</time>
Valid: Simple time value
<time datetime="14:30">2:30 PM</time>
Invalid: Non-standard duration
<time datetime="1 hour 30 minutes">1.5 hours</time>
Valid: ISO 8601 duration
<time datetime="PT1H30M">1.5 hours</time>
Valid: Date-time with timezone offset
<p>The event starts at <time datetime="2024-03-15T14:30-05:00">2:30 PM ET on March 15</time>.</p>
Valid: Using only the month
<p>Published in <time datetime="2024-03">March 2024</time>.</p>
Remember that the human-readable text content between the <time> tags can be in any format you like — it’s only the datetime attribute value that must follow the specification. This lets you display dates in a user-friendly way while still providing a standardized machine-readable value.
Boolean attributes in HTML work differently from regular attributes. Their mere presence on an element makes them “true,” and their absence makes them “false.” According to the WHATWG HTML specification, a boolean attribute may only have three valid representations:
- The attribute name alone (e.g., disabled)
- The attribute with an empty string value (e.g., disabled="")
- The attribute with its own name as the value (e.g., disabled="disabled")
Any other value — including seemingly intuitive ones like "true", "yes", or "no" — is invalid and will cause the W3C HTML Validator to report an error such as: Bad value “disabled” for attribute “disabled” on element “input” (or a similar message referencing whatever invalid value you used).
Why this matters
Standards compliance: Using invalid values violates the HTML specification, which can lead to unpredictable behavior as browsers evolve.
Misleading behavior: A common pitfall is writing disabled="false" and expecting the input to be enabled. This does not work as expected — because the attribute is still present, the element remains disabled regardless of the value. This can lead to confusing bugs where developers think they’re enabling a field but it stays disabled.
Accessibility: Assistive technologies rely on the DOM’s interpretation of boolean attributes. While browsers typically handle invalid values gracefully by treating any present disabled attribute as true, sticking to valid values ensures the most consistent behavior across screen readers and other tools.
Templating and frameworks: This issue frequently arises when templating engines or JavaScript frameworks insert string values into boolean attributes. If your template outputs disabled="true" or disabled="false", you should instead conditionally include or omit the attribute entirely.
How to fix it
- Remove the value entirely — just write the attribute name by itself.
- Use an empty string — write disabled="" if your tooling requires an explicit value.
- Use the canonical form — write disabled="disabled" if you need XHTML compatibility.
- To enable an element, remove the disabled attribute completely rather than setting it to "false".
Examples
Incorrect usage
These all trigger a validation error because the values are not valid for a boolean attribute:
<input type="text" disabled="yes">
<input type="text" disabled="true">
<input type="text" disabled="false">
<input type="text" disabled="1">
<button disabled="no">Submit</button>
Note that disabled="false" and disabled="no" still disable the element — the browser sees the attribute is present and treats it as true.
Correct usage
All three of these are valid ways to disable an input:
<input type="text" disabled>
<input type="text" disabled="">
<input type="text" disabled="disabled">
To have an enabled input, simply omit the attribute:
<input type="text">
Handling dynamic values in JavaScript
If you need to toggle the disabled state dynamically, use the DOM property rather than setting an attribute value:
<form>
<input type="text" id="username">
<button type="button" id="toggle">Toggle</button>
<script>
document.getElementById("toggle").addEventListener("click", function () {
var input = document.getElementById("username");
input.disabled = !input.disabled;
});
</script>
</form>
Setting element.disabled = true or element.disabled = false in JavaScript correctly adds or removes the attribute from the DOM without producing invalid markup.
Other boolean attributes
This same rule applies to all boolean attributes in HTML, including checked, readonly, required, hidden, autoplay, loop, muted, and others. For example:
<!-- Incorrect -->
<input type="checkbox" checked="true">
<input type="email" required="required_field">
<!-- Correct -->
<input type="checkbox" checked>
<input type="email" required>
When in doubt, use the simplest form: just the attribute name with no value. It’s the most readable, the most concise, and fully compliant with the HTML specification.
The <label> element represents a caption for a form control. When you use the for attribute, its value must match the id of the form control it labels. According to the HTML specification, an id attribute value must not contain any whitespace characters — this includes spaces, tabs, line feeds, carriage returns, and form feeds. Since for must reference a valid ID, the same restriction applies to its value.
This error typically occurs when a developer uses a space-separated name (like "user name" or "first name") instead of a single continuous token. Browsers may fail to establish the programmatic association between the label and its form control when the for value contains whitespace. This directly impacts accessibility: screen readers rely on this association to announce the label text when a user focuses on the input. It also breaks the click-to-focus behavior where clicking a label moves focus to its associated control.
To fix this issue, replace any whitespace in the for attribute value with a valid character such as a hyphen (-), underscore (_), or camelCase. Make sure the id on the corresponding form control matches exactly.
Examples
Incorrect — whitespace in the for attribute
<form>
<label for="user name">Name</label>
<input type="text" id="user name">
</form>
The value "user name" contains a space, which makes it an invalid ID reference. The validator will report: Bad value “user name” for attribute “for” on element “label”: An ID must not contain whitespace.
Correct — using an underscore instead of a space
<form>
<label for="user_name">Name</label>
<input type="text" id="user_name">
</form>
Correct — using a hyphen instead of a space
<form>
<label for="user-name">Name</label>
<input type="text" id="user-name">
</form>
Correct — using camelCase
<form>
<label for="userName">Name</label>
<input type="text" id="userName">
</form>
Incorrect — leading or trailing whitespace
Whitespace doesn’t have to be in the middle of the value to trigger this error. Leading or trailing spaces also make the ID invalid:
<form>
<label for=" email">Email</label>
<input type="text" id=" email">
</form>
This can be easy to miss when values are generated dynamically or copied from another source. Trim the value to fix it:
<form>
<label for="email">Email</label>
<input type="text" id="email">
</form>
Alternative — wrapping the input inside the label
If you wrap the form control inside the <label> element, you don’t need the for attribute at all. The association is implicit:
<form>
<label>
Name
<input type="text">
</label>
</form>
This approach avoids potential ID mismatches entirely, though explicit for/id pairing is often preferred for flexibility in layout and styling.
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" to height="650". This is the simplest fix and keeps your sizing in the markup.
-
Move sizing to CSS. Remove the height attribute entirely and use a stylesheet or inline style attribute 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.
<embed src="file.pdf" type="application/pdf" width="800" height="650px">
Other invalid variations include:
<embed src="file.pdf" type="application/pdf" height="100%">
<embed src="file.pdf" type="application/pdf" height="40em">
<embed src="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.
<embed src="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.
<embed class="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.
<embed src="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 height attribute on <iframe> is defined in the HTML specification as a “valid non-negative integer.” This means the value must be a string of one or more digit characters (0 through 9) and nothing else. Unlike CSS properties, this attribute does not accept units like px or %, nor does it accept decimal values like 315.5. Even invisible characters such as leading or trailing spaces will trigger this validation error because the parser expects a digit and encounters something else.
This matters for several reasons. While browsers are generally forgiving and may still render the iframe correctly despite an invalid value, relying on error recovery behavior is fragile and may not work consistently across all browsers or future versions. Invalid attribute values can also cause unexpected results in automated tools, screen readers, and other user agents that parse HTML strictly. Writing valid, standards-compliant markup ensures predictable behavior everywhere.
Common causes of this error include:
- Adding CSS units to the attribute value (e.g., height="315px")
- Using percentages (e.g., height="100%")
- Decimal values (e.g., height="315.5")
- Whitespace before or after the number (e.g., height=" 315" or height="315 ")
- Copy-paste artifacts introducing hidden characters
If you need percentage-based or decimal sizing, use CSS instead of the HTML attribute. The height attribute only accepts whole pixel values.
Examples
❌ Invalid: Using px unit in the attribute
<iframe width="560" height="315px" src="https://example.com/video"></iframe>
The validator sees the p character after 315 and reports the error.
❌ Invalid: Using a percentage
<iframe width="100%" height="100%" src="https://example.com/video"></iframe>
Percentage values are not allowed in the height attribute.
❌ Invalid: Decimal value
<iframe width="560" height="315.5" src="https://example.com/video"></iframe>
The decimal point is not a digit, so the validator rejects it.
❌ Invalid: Leading or trailing whitespace
<iframe width="560" height=" 315 " src="https://example.com/video"></iframe>
The space before 315 is encountered where a digit is expected.
✅ Valid: Digits only
<iframe width="560" height="315" src="https://example.com/video"></iframe>
The value 315 contains only digits and is a valid non-negative integer.
✅ Valid: Using CSS for percentage-based sizing
If you need the iframe to scale responsively, remove the height attribute and use CSS instead:
<iframe
width="560"
height="315"
src="https://example.com/video"
style="width: 100%; height: 100%;"
></iframe>
Or better yet, apply styles through a stylesheet:
<style>
.responsive-iframe {
width: 100%;
height: 400px;
}
</style>
<iframe class="responsive-iframe" src="https://example.com/video"></iframe>
This keeps the HTML attributes valid while giving you full control over sizing through CSS, including support for units like %, vh, em, and calc().
The height attribute on <video> is defined in the HTML specification as a “valid non-negative integer.” This means it must be a string of digits (e.g., "480") with no units, no decimal points, and no percentage signs. When you write something like height="50%", the W3C validator rejects it because % is not a digit character and percentage values are not part of the attribute’s allowed syntax.
This same rule applies to the width attribute on <video>, as well as the width and height attributes on <img>, <canvas>, and other elements that accept pixel dimensions. The integer you provide represents a size in CSS pixels, so height="480" means 480 CSS pixels — no unit suffix is needed or allowed.
Why this matters
- Standards compliance: The HTML living standard explicitly defines these attributes as non-negative integers. Using percentages violates the spec and produces a validation error.
- Browser behavior: While some browsers may attempt to interpret a percentage value, the behavior is undefined and inconsistent. You cannot rely on it working correctly across all browsers and versions.
- Layout stability: Providing valid width and height integer values helps browsers reserve the correct aspect ratio for the video before it loads, reducing cumulative layout shift (CLS). Invalid values undermine this benefit.
How to fix it
- Replace the percentage value with a plain integer representing the desired pixel height.
- If you need percentage-based or responsive sizing, remove the percentage from the HTML attribute and use CSS instead. CSS width and height properties fully support percentage values, viewport units, and other flexible sizing methods.
- Always keep integer width and height attributes on the element when possible, even if you also use CSS for responsive sizing. This provides an intrinsic aspect ratio hint to the browser.
Examples
❌ Invalid: percentage value in the height attribute
<video controls width="100%" height="50%">
<source src="/media/video.webm" type="video/webm">
</video>
The validator reports: Bad value “50%” for attribute “height” on element “video”: Expected a digit but saw “%” instead. The same error applies to the width="100%" attribute.
✅ Fixed: integer values for intrinsic dimensions
<video controls width="640" height="480">
<source src="/media/video.webm" type="video/webm">
</video>
Both width and height use plain integers representing CSS pixels. This is valid and gives the browser an aspect ratio hint (4:3 in this case).
✅ Fixed: responsive sizing with CSS
If you need the video to scale as a percentage of its container, use CSS for the sizing while keeping valid integer attributes for the aspect ratio:
<video controls width="640" height="360" style="width: 100%; height: auto;">
<source src="/media/video.webm" type="video/webm">
</video>
Here, width="640" and height="360" tell the browser the video’s intrinsic aspect ratio (16:9), while the inline style (or an external stylesheet) makes the video fill 100% of its container width and scale its height proportionally. This approach is both valid HTML and fully responsive.
✅ Fixed: using a CSS class for responsive video
<style>
.responsive-video {
width: 100%;
max-width: 800px;
height: auto;
}
</style>
<video controls width="800" height="450" class="responsive-video">
<source src="/media/video.webm" type="video/webm">
</video>
This keeps the HTML valid, provides an aspect ratio hint, and achieves flexible, percentage-based sizing entirely through CSS.
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 href values 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
<a href="docs\guide\intro.html">Introduction</a>
Correct: forward slash used as path delimiter
<a href="docs/guide/intro.html">Introduction</a>
Incorrect: backslashes in an absolute URL
<a href="https://example.com\blog\2024\post.html">Read the post</a>
Correct: forward slashes in an absolute URL
<a href="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:
<a href="assets/images\photo.jpg">View photo</a>
Correct: all forward slashes
<a href="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:// not https:/ or https:.
- 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
<a href="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
<a href="https:example.com">Visit Example</a>
This is also invalid — the colon must be followed by // for https URLs.
Correct: Properly formatted URL
<a href="https://example.com">Visit Example</a>
Correct: URL with a path
<a href="https://example.com/blog/my-post">Read the Post</a>
Correct: URL with encoded spaces
<a href="https://example.com/search?q=hello%20world">Search</a>
Incorrect: Unencoded space in URL
<a href="https://example.com/my page">My Page</a>
Spaces are not valid in URLs. Use %20 or + (in query strings) instead:
<a href="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 %7B and } 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
<a href="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:
<a href="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:
<a href="https://example.com/api/users/42/profile">View Profile</a>
Incorrect: Curly braces in a fragment identifier
<a href="https://example.com/docs#section-{name}">Jump to Section</a>
Correct: Percent-encoded fragment
<a href="https://example.com/docs#section-%7Bname%7D">Jump to Section</a>
Incorrect: Curly braces in query parameters
<a href="https://example.com/search?filter={active}">Active Items</a>
Correct: Percent-encoded query parameter
<a href="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:
<a id="dynamic-link" href="https://example.com">Dynamic Link</a>
<script>
var value = "{some-value}";
var link = document.getElementById("dynamic-link");
link.href = "https://example.com/path?param=" + encodeURIComponent(value);
</script>
This ensures that any special characters, including curly braces, are automatically percent-encoded in the resulting URL.
A URL fragment identifier is the part of a URL that follows the # symbol, typically used to link to a specific section within a page. The URL specification (RFC 3986 and the WHATWG URL Standard) defines a strict set of characters that are permitted in fragments. The > character (greater-than sign) is among those that are not allowed to appear literally. When the W3C HTML Validator encounters > inside an href value — whether in the fragment or elsewhere in the URL — it raises this error.
In most cases, the > character appears in a URL by accident — for example, from a copy-paste error, a typo where the closing > of the tag accidentally ends up inside the attribute value, or a malformed template expression. Less commonly, a developer may genuinely need the > character as part of the URL content.
Why This Matters
- Broken links: Browsers may interpret the > as the end of the HTML tag or handle the URL unpredictably, leading to broken navigation.
- Standards compliance: Invalid URLs violate both the HTML specification and URL syntax standards, causing validation failures.
- Accessibility: Screen readers and other assistive technologies rely on well-formed markup. A malformed href can confuse these tools and prevent users from reaching the intended destination.
- Interoperability: While some browsers may silently correct or ignore the invalid character, others may not. Valid URLs guarantee consistent behavior everywhere.
How to Fix It
- If the > is a typo or copy-paste error, simply remove it from the href value. This is the most common scenario.
- If the > is intentionally part of the fragment or URL, replace it with its percent-encoded equivalent: %3E.
- Review your templates or CMS output — this error often originates from template engines or content management systems that inject malformed values into href attributes.
Examples
Accidental > in the URL path
A common mistake is accidentally including the tag’s closing > inside the attribute value:
<!-- ❌ Invalid: ">" is not allowed in the URL -->
<a href="/page.php>">Read more</a>
Remove the stray >:
<!-- ✅ Valid: clean URL without illegal character -->
<a href="/page.php">Read more</a>
Illegal > in a fragment identifier
The > can also appear inside the fragment portion of the URL:
<!-- ❌ Invalid: ">" in the fragment -->
<a href="/docs#section->overview">Go to overview</a>
If the > is unintentional, remove it:
<!-- ✅ Valid: fragment without illegal character -->
<a href="/docs#section-overview">Go to overview</a>
If the > is genuinely needed in the fragment, percent-encode it:
<!-- ✅ Valid: ">" encoded as %3E -->
<a href="/docs#section-%3Eoverview">Go to overview</a>
Query string or path containing >
The same rule applies outside of fragments. If > appears anywhere in the URL, encode it:
<!-- ❌ Invalid: ">" in query parameter -->
<a href="/search?filter=price>100">Expensive items</a>
<!-- ✅ Valid: ">" percent-encoded as %3E -->
<a href="/search?filter=price%3E100">Expensive items</a>
Template output errors
If you are using a server-side template or JavaScript framework, ensure that dynamic values inserted into href are properly URL-encoded. For example, in a template that generates links:
<!-- ❌ Invalid: unencoded dynamic value -->
<a href="/results#filter=>50">View results</a>
<!-- ✅ Valid: properly encoded value -->
<a href="/results#filter=%3E50">View results</a>
Most server-side languages provide built-in URL encoding functions (e.g., encodeURIComponent() in JavaScript, urlencode() in PHP, urllib.parse.quote() in Python) that will handle this automatically. Always use these functions when inserting dynamic content into URLs.
A URL can have only one fragment identifier — the portion that comes after the single # symbol. When the browser encounters a # in a URL, it treats everything after it as the fragment. If a second # appears, it becomes an illegal character within that fragment because the fragment portion of a URL does not allow unencoded # characters. This violates the URL specification (WHATWG) and the HTML standard’s requirements for valid URLs in the href attribute.
This matters for several reasons:
- Standards compliance: Browsers may handle malformed URLs inconsistently, leading to unpredictable navigation behavior across different environments.
- Accessibility: Screen readers and assistive technologies rely on well-formed URLs to correctly announce link destinations and allow users to navigate.
- SEO and crawling: Search engine crawlers may fail to follow or index pages with invalid URLs.
The # character is perfectly valid when used exactly once to introduce a fragment. For example, #pricing or /faqs#pricing are fine. The issue arises when a second # appears, such as /faqs#guarantee#pricing, or when # is used in a part of the URL where it isn’t expected.
If you genuinely need a literal # character as part of a path or query string (not as a fragment delimiter), you must percent-encode it as %23.
Examples
❌ Invalid: Multiple # characters in the URL
This triggers the validator error because the fragment (guarantee#pricing) contains an illegal #:
<a href="/faqs#guarantee#pricing">Guarantee and Pricing</a>
✅ Fixed: Use a single fragment identifier
Link to one section at a time with a single #:
<a href="/faqs#guarantee">Guarantee</a>
<a href="/faqs#pricing">Pricing</a>
✅ Fixed: Encode the # if it’s meant as a literal character
If the # is part of the actual path or data (not a fragment delimiter), encode it as %23:
<a href="/faqs%23guarantee#pricing">Pricing</a>
In this case, /faqs%23guarantee is the path (containing a literal #), and pricing is the fragment.
Using fragments correctly in a table of contents
Fragments work well for in-page navigation. Each target element needs a matching id:
<nav>
<ul>
<li><a href="#pricing">Pricing</a></li>
<li><a href="#terms">Terms</a></li>
<li><a href="#guarantee">Guarantee</a></li>
</ul>
</nav>
<h2 id="pricing">Pricing</h2>
<p>All about pricing...</p>
<h2 id="terms">Terms</h2>
<p>You can find our terms at...</p>
<h2 id="guarantee">Guarantee</h2>
<p>We offer a guarantee...</p>
❌ Invalid: # in a query string or path without encoding
Sometimes this error appears when a URL accidentally includes an unencoded # in the wrong place:
<a href="/search?color=#ff0000">Red items</a>
✅ Fixed: Encode the # in the query value
<a href="/search?color=%23ff0000">Red items</a>
Here, %23ff0000 correctly represents the literal string #ff0000 as a query parameter value, rather than being misinterpreted as the start of a fragment.
Common causes
- Copy-pasting URLs from other sources that contain multiple # characters.
- Dynamically generated links where fragment values aren’t properly sanitized.
- Color codes in URLs like #ff0000 that need to be percent-encoded as %23ff0000.
- Concatenation errors in templates where a # is added both in the base URL and the appended fragment.
The fix is always the same: make sure your href contains at most one # used as the fragment delimiter, and percent-encode (%23) any # that is meant as a literal character.
The fragment portion of a URL (everything after the # symbol) follows the same encoding rules as the rest of the URL — literal space characters are not permitted. When a browser encounters a space in a URL fragment, it may try to correct it automatically, but this behavior is not guaranteed across all browsers and contexts. Relying on browser error-correction leads to fragile links that may break unpredictably.
This issue matters for several reasons. First, it produces invalid HTML that fails W3C validation. Second, fragment navigation (jumping to a specific section of a page) may not work correctly if the browser doesn’t auto-correct the space. Third, assistive technologies like screen readers rely on well-formed URLs to announce link destinations accurately. Finally, tools that parse or process HTML programmatically — such as crawlers, link checkers, and content management systems — may misinterpret or reject malformed URLs.
The most common scenario for this error is linking to an id attribute on the same page or another page where the id contains spaces. However, it’s worth noting that id values themselves cannot contain spaces in valid HTML (a space in an id would make it multiple invalid tokens). So if you’re writing both the link and the target, the best fix is often to correct the id itself by using hyphens or underscores instead of spaces.
How to Fix It
There are two main approaches:
- Percent-encode the spaces — Replace each space with %20 in the href value.
- Use space-free identifiers — Change the target id to use hyphens or camelCase, then update the fragment to match.
The second approach is strongly preferred because it fixes the root problem and produces cleaner, more readable markup.
Examples
❌ Invalid: Space in the fragment
<a href="https://example.com/page#some term">Go to section</a>
<a href="#my section">Jump to My Section</a>
✅ Fixed: Percent-encode the space
If you cannot control the target id (e.g., linking to an external page), percent-encode the space:
<a href="https://example.com/page#some%20term">Go to section</a>
✅ Better fix: Use a hyphenated id and matching fragment
When you control both the link and the target, use a space-free id:
<h2 id="my-section">My Section</h2>
<p>Some content here.</p>
<a href="#my-section">Jump to My Section</a>
❌ Invalid: Space in fragment linking to another page
<a href="/docs/getting-started#quick start guide">Quick Start Guide</a>
✅ Fixed: Matching hyphenated id
<!-- On the target page (/docs/getting-started): -->
<h2 id="quick-start-guide">Quick Start Guide</h2>
<!-- On the linking page: -->
<a href="/docs/getting-started#quick-start-guide">Quick Start Guide</a>
A note on id naming conventions
Since fragment identifiers reference id attributes, adopting a consistent id naming convention avoids this issue entirely. Common patterns include:
<!-- Hyphens (most common, used by many static site generators) -->
<section id="getting-started">
<!-- camelCase -->
<section id="gettingStarted">
<!-- Underscores -->
<section id="getting_started">
All three are valid and will never trigger a space-related validation error in your fragment links.
URLs follow strict syntax rules defined by RFC 3986, which does not permit literal space characters anywhere in a URI. When the W3C validator encounters a space in the href attribute of an <a> element — particularly within the query string (the part after the ?) — it flags it as an illegal character.
While most modern browsers will silently fix malformed URLs by encoding spaces for you, relying on this behavior is problematic for several reasons:
- Standards compliance: The HTML specification requires that href values contain valid URLs. A space makes the URL syntactically invalid.
- Interoperability: Not all user agents, crawlers, or HTTP clients handle malformed URLs the same way. Some may truncate the URL at the first space or reject it entirely.
- Accessibility: Screen readers and assistive technologies may struggle to interpret or announce links with invalid URLs.
- Link sharing and copy-pasting: If a user copies the link from the source or if the URL is used in an API or redirect, the unencoded space can cause breakage.
To fix this issue, replace every literal space character in the URL with %20. Within query string values, you can also use + as a space encoding (this is the application/x-www-form-urlencoded format commonly used in form submissions). If you’re generating URLs dynamically, use your programming language’s URL encoding function (e.g., encodeURIComponent() in JavaScript, urlencode() in PHP, or urllib.parse.quote() in Python).
Examples
Incorrect — spaces in the query string
<a href="https://example.com/search?query=hello world&lang=en">Search</a>
The space between hello and world is an illegal character in the URL.
Correct — space encoded as %20
<a href="https://example.com/search?query=hello%20world&lang=en">Search</a>
Correct — space encoded as + in query string
<a href="https://example.com/search?query=hello+world&lang=en">Search</a>
Using + to represent a space is valid within query strings and is commonly seen in URLs generated by HTML forms.
Incorrect — spaces in the path and query
<a href="https://example.com/my folder/page?name=John Doe">Profile</a>
Correct — all spaces properly encoded
<a href="https://example.com/my%20folder/page?name=John%20Doe">Profile</a>
Generating safe URLs in JavaScript
If you’re building URLs dynamically, use encodeURIComponent() for individual parameter values:
<script>
const query = "hello world";
const url = "https://example.com/search?query=" + encodeURIComponent(query);
// Result: "https://example.com/search?query=hello%20world"
</script>
Note that encodeURIComponent() encodes spaces as %20, which is safe for use in any part of a URL. Avoid using encodeURI() for query values, as it does not encode certain characters like & and = that may conflict with query string syntax.
The URL standard defines a specific set of characters that are allowed to appear literally in a URL’s query string. Characters outside this allowed set — such as |, [, ], {, }, ^, and unencoded spaces — must be percent-encoded. Percent-encoding replaces the character with a % sign followed by its two-digit hexadecimal ASCII code.
This matters for several reasons. Browsers may handle illegal characters inconsistently — some might silently fix them while others may not, leading to broken links. Screen readers and assistive technologies rely on well-formed URLs to properly announce link destinations. Search engine crawlers may also fail to follow URLs with illegal characters, which can hurt discoverability. Using properly encoded URLs ensures your links work reliably across all user agents and conform to both the HTML and URL specifications.
Common Characters That Need Encoding
Here are frequently encountered characters that trigger this validation error:
| Character | Percent-Encoded |
|---|---|
| (space) | %20 (or + in query strings) |
| | | %7C |
| [ | %5B |
| ] | %5D |
| { | %7B |
| } | %7D |
| ^ | %5E |
Note that characters like ?, =, &, and # have special meaning in URLs and are allowed in their respective positions. For example, ? starts the query string, & separates parameters, and = separates keys from values. However, if these characters appear as part of a parameter’s value (rather than as delimiters), they must also be percent-encoded.
How to Fix It
- Identify the illegal character mentioned in the validator’s error message.
- Replace it with its percent-encoded equivalent.
- If you’re generating URLs dynamically with a server-side language, use built-in encoding functions like encodeURIComponent() in JavaScript, urlencode() in PHP, or urllib.parse.quote() in Python.
Examples
❌ Incorrect: Unencoded pipe character in query string
<a href="https://example.com/search?filter=red|blue">Search</a>
✅ Correct: Pipe character percent-encoded as %7C
<a href="https://example.com/search?filter=red%7Cblue">Search</a>
❌ Incorrect: Unencoded square brackets in query string
<a href="https://example.com/api?items[0]=apple&items[1]=banana">View items</a>
✅ Correct: Square brackets percent-encoded
<a href="https://example.com/api?items%5B0%5D=apple&items%5B1%5D=banana">View items</a>
❌ Incorrect: Unencoded space in query string
<a href="https://example.com/search?q=hello world">Search</a>
✅ Correct: Space encoded as %20
<a href="https://example.com/search?q=hello%20world">Search</a>
If you’re building URLs in JavaScript, use encodeURIComponent() on individual parameter values rather than encoding the entire URL. This function handles all characters that need encoding while leaving the URL structure intact:
const query = "red|blue";
const url = `https://example.com/search?filter=${encodeURIComponent(query)}`;
// Result: "https://example.com/search?filter=red%7Cblue"
Avoid using encodeURI() for this purpose, as it does not encode characters like [, ], or | that are illegal in query strings. Always use encodeURIComponent() for encoding individual query parameter names and values.
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