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.
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>:
<div role="tablist" aria-label="Recipe categories">
<button role="tab" aria-controls="panel-1" aria-selected="true" id="tab-1">Appetizers</button>
<button role="tab" aria-controls="panel-2" aria-selected="false" id="tab-2">Desserts</button>
</div>
<ul role="tabpanel" id="panel-1" aria-labelledby="tab-1">
<li>Bruschetta</li>
<li>Spring rolls</li>
</ul>
<ul role="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:
<div role="tablist" aria-label="Recipe categories">
<button role="tab" aria-controls="panel-1" aria-selected="true" id="tab-1">Appetizers</button>
<button role="tab" aria-controls="panel-2" aria-selected="false" id="tab-2">Desserts</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
<ul>
<li>Bruschetta</li>
<li>Spring rolls</li>
</ul>
</div>
<div role="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:
<div role="tablist" aria-label="Project info">
<button role="tab" aria-controls="tasks-panel" aria-selected="true" id="tasks-tab">Tasks</button>
<button role="tab" aria-controls="notes-panel" aria-selected="false" id="notes-tab">Notes</button>
</div>
<section role="tabpanel" id="tasks-panel" aria-labelledby="tasks-tab">
<h2>Current tasks</h2>
<ul>
<li>Review pull requests</li>
<li>Update documentation</li>
</ul>
</section>
<section role="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 content attribute of a <meta http-equiv="Content-Type"> tag ends at text/html;, with no character encoding after the semicolon.
With http-equiv="Content-Type", the <meta> element is an encoding declaration, and the HTML standard allows one shape for its content: text/html;, optional spaces, then charset=utf-8. The validator reads text/html;, hits the end of the value right after the semicolon, and reports that the declaration ended too early. Values cut off later get different messages: text/html; charset is reported as missing charset=, and text/html; charset= as an empty encoding name.
Browsers take an encoding from this tag only when they find charset= in it, so a bare text/html; does nothing. If the server's Content-Type header has no charset either, the browser falls back to its own default, and accented letters or symbols can come out garbled.
Add charset=utf-8 after the semicolon, or swap the tag for the shorter <meta charset="utf-8">, which declares the same thing. Keep only one of the two forms in the document. The file itself also has to be saved as UTF-8.
Invalid example
<head>
<meta http-equiv="Content-Type" content="text/html;">
<title>Products</title>
</head>
Valid example
<head>
<meta charset="utf-8">
<title>Products</title>
</head>
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
<div aria-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
<div aria-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
<div aria-hidden="true">
This content is hidden from assistive tech
</div>
Correct — simple false value
<div aria-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:
<div id="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) -->
<div aria-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>
<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.
The path in a <form> element's action can't contain a literal space, so write each space as %20 or rename the target so its URL has none.
The action attribute holds the URL that receives the submitted form data, and the URL Standard doesn't allow spaces in a path. The validator reports it for values like /contact us/send.php or ./privacy policy.aspx. Accented letters such as í or ñ are allowed in a path, so only the space needs fixing.
Some server frameworks write the current page's file name into action when a form posts back to the same page. If that file name has a space in it, every page built from it reports this error, and renaming the file or route fixes all of them at once.
Browsers percent-encode the space before they submit, so the form usually still works. A crawler or a script that reads the raw value may not do the same, and the markup is invalid either way. Spaces at the very start or end of the value are allowed, because the URL parser strips them. A space after the ? gives a different message, Illegal character in query, and %20 fixes that one too.
Invalid example
<form method="post" action="/contact us/send.php">
<label>
<input type="email" name="email">
</label>
<button type="submit">Send</button>
</form>
Valid example: space encoded as %20
<form method="post" action="/contact%20us/send.php">
<label>
<input type="email" name="email">
</label>
<button type="submit">Send</button>
</form>
Valid example: hyphen instead of a space
<form method="post" action="/contact-us/send.php">
<label>
<input type="email" name="email">
</label>
<button type="submit">Send</button>
</form>
The aria-multiselectable attribute only accepts the values true and false, written in lowercase with no spaces around them.
aria-multiselectable tells assistive technologies whether users can select more than one item in a widget. It belongs on elements with the role listbox, grid, tablist, tree or treegrid, and its default value is false. Screen readers use it to announce a list as multi-select, so users know they can pick several options.
The validator rejects every other value. Leading spaces (" true"), a trailing space ("true "), capital letters ("TRUE"), words like "yes", the number "1" and an empty value all trigger this error. Browsers may treat a value they don't recognize as the default, so screen readers announce the widget as single-select even when the script lets users pick several items. Stray spaces often come from a template that prints whitespace or a line break before the value it inserts.
Change the value to exactly true or false. If the widget allows only one selection, you can also drop the attribute, since false is the default. On a div with no role, the attribute has no meaning, and the validator also reports that the div is missing a role attribute.
Invalid example
<div role="listbox" aria-label="Toppings" aria-multiselectable=" true">
<div role="option" aria-selected="true">Cheese</div>
<div role="option" aria-selected="false">Olives</div>
</div>
Valid example
<div role="listbox" aria-label="Toppings" aria-multiselectable="true">
<div role="option" aria-selected="true">Cheese</div>
<div role="option" aria-selected="false">Olives</div>
</div>
The aria-orientation attribute only accepts horizontal, vertical or undefined, and the value on this element is none of them.
aria-orientation tells assistive technologies which way a widget's items are laid out, so a screen reader user knows whether the up and down arrow keys or the left and right ones move between them. It applies to roles such as tablist, toolbar, slider, separator, listbox and menu. The validator compares the value exactly, so an empty string, a capitalized Horizontal, a value with spaces around it or a made-up keyword like both all trigger this error. A value outside that list gives assistive technologies no orientation they can use.
An empty value usually comes from a component or template that always prints the attribute, even when no orientation was set.
To fix it, set the value to horizontal or vertical in lowercase, matching how the items are laid out on screen. If the role's default already matches, such as horizontal for a tablist or toolbar, you can remove the attribute instead. On a div without a role, the attribute does nothing and the validator also asks for a role, so either add the widget role or drop the attribute.
Invalid example
<div role="toolbar" aria-label="Text formatting" aria-orientation="">
<button type="button">Bold</button>
<button type="button">Italic</button>
</div>
Valid example
<div role="toolbar" aria-label="Text formatting" aria-orientation="vertical">
<button type="button">Bold</button>
<button type="button">Italic</button>
</div>
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.
The autocomplete attribute of an <input> contains a word that isn't one of the autofill field names defined in the HTML standard.
autocomplete doesn't take free text. It takes on, off, or tokens from a fixed list such as name, email, organization, postal-code or tel, optionally preceded by a section-* name and by shipping or billing. Browsers and password managers read those tokens to fill the field with data the user has saved. A word outside the list gives them no hint about what the field holds.
This usually happens when the field's own name ends up in autocomplete, often in the language of the page, such as unidade-saude on a Portuguese form. Autofill field names are fixed English tokens, whatever the language of the page. When the value has several tokens, the message quotes only the one the validator doesn't recognize.
If one of the standard tokens describes the field, use it. If none does, as with a clinic or department name, set autocomplete="off" or remove the attribute, and keep your own identifier in name or id.
Invalid example
<label for="clinic">Clinic</label>
<input type="text" id="clinic" name="clinic" autocomplete="clinic-name">
Valid example
<label for="clinic">Clinic</label>
<input type="text" id="clinic" name="clinic" autocomplete="off">
When a standard token fits the field, use it:
<label for="company">Company</label>
<input type="text" id="company" name="company" autocomplete="organization">
The colspan attribute of a <td> takes digits only, so a single space after the number, as in colspan="2 ", makes the value invalid.
The space usually comes from a template. In <td colspan="{{ span }} "> or <td colspan="<?php echo $span; ?> ">, the space before the closing quote ends up in the attribute, right after the number. A helper that pads numbers to a fixed width has the same effect. A space before the number (" 2") or a line break inside the quotes gets the same error. The character after "saw" is the first one that isn't a digit: a blank here, p in 2px, % in 50%. A value with markup in it, like 2><span class=, means a closing quote is missing.
The HTML standard requires colspan to be a valid non-negative integer greater than zero and no larger than 1000, which is one or more digits with nothing around them. Browsers skip leading spaces, read the digits and ignore the rest, so "2 " still spans two columns. The validator's table check counts a cell with a trailing space as one column, though, so a "table row was N columns wide" warning often appears alongside this error.
Fix the template so the closing quote comes right after the number, or trim the value before printing it.
Invalid example
<table>
<tr>
<th>Item</th>
<th>Qty</th>
<th>Price</th>
</tr>
<tr>
<td>Notebook</td>
<td>2</td>
<td>$8</td>
</tr>
<tr>
<td colspan="2 ">Total</td>
<td>$8</td>
</tr>
</table>
Valid example
<table>
<tr>
<th>Item</th>
<th>Qty</th>
<th>Price</th>
</tr>
<tr>
<td>Notebook</td>
<td>2</td>
<td>$8</td>
</tr>
<tr>
<td colspan="2">Total</td>
<td>$8</td>
</tr>
</table>
The colspan attribute of a <th> takes a positive whole number written with digits only, and the validator names the first character in the value that isn't a digit.
colspan sets how many columns a header cell spans. The HTML standard requires a valid non-negative integer greater than zero and no larger than 1000, such as 2 or 3. Units, percentages, decimals, signs, spaces, quotes and keywords like auto all make the value invalid. The character after "saw" is where the number stopped: p in 2px, % in 100%, . in 1.5, - in -1.
A value that contains markup, like 2><span class=, usually means a quote is missing. In <th colspan="2><span class="label">, nothing closes the quote after 2, so the parser reads on to the next " and puts everything in between into colspan. The <span> start tag is lost, and what's left of it becomes stray attributes on the <th>. That's why errors like "No space between attributes" and "Quote in attribute name" usually appear next to this one. When the number is missing too, the value starts right at the >. A CMS or rich text editor can store the broken attribute with the < escaped as <, so check the saved content as well as the template.
Browsers read the leading digits and ignore the rest, so 2px still spans two columns. A value that doesn't start with a digit, or is zero, falls back to 1. The header then covers one column and every cell after it in the row moves over, so screen readers can announce data cells under the wrong header.
Write the number on its own, without a unit, and close the attribute's quote before the >.
Invalid example
<table>
<tr>
<th colspan="2px">Name</th>
<th colspan="2><span class="label">Contact</span></th>
</tr>
<tr>
<td>Ada</td>
<td>Lovelace</td>
<td>ada@example.com</td>
<td>555-0100</td>
</tr>
</table>
Valid example
<table>
<tr>
<th colspan="2">Name</th>
<th colspan="2"><span class="label">Contact</span></th>
</tr>
<tr>
<td>Ada</td>
<td>Lovelace</td>
<td>ada@example.com</td>
<td>555-0100</td>
</tr>
</table>
The content value of a <meta http-equiv="refresh"> tag needs at least one space between the semicolon and URL=, as in 0; URL=https://example.com/.
For a refresh, the HTML standard defines content as a number of seconds, optionally followed by a semicolon, one or more spaces, URL= and the address to load. The validator reads the delay, finds the semicolon and expects whitespace next. Whatever character it finds instead ends up in the message: usually U or u, or the first character of the address when URL= is missing as well.
Browsers are more lenient and follow 0;URL=... anyway, so the redirect works despite the error. Adding a space after the semicolon fixes it, along with URL= if the value doesn't have one. URL is case-insensitive, so url= passes too, but don't wrap the address in quotes.
It's also worth checking whether the page needs the tag at all. An automatic refresh or a delayed redirect changes the page without the user asking for it, which WCAG flags under 2.2.1 (Timing Adjustable) and 3.2.5 (Change on Request). A 0-second redirect avoids the timing problem. Still, when a page has moved for good, a server-side 301 redirect is better, because the browser follows it before loading any HTML.
Invalid example
<meta http-equiv="refresh" content="0;URL=https://example.com/">
Valid example
<meta http-equiv="refresh" content="0; URL=https://example.com/">
The coords value contains a space where the validator expects the start of a number.
An <area> element defines a region in an image map. Its coords attribute describes that region using coordinates in CSS pixels. For shape="poly", provide at least three pairs of x and y coordinates, with an even number of values.
In the example below, Rocket's HTML validator rejects the space immediately after the second comma. Write the coordinates as comma-separated numbers without spaces to avoid this parsing error. Keep the values appropriate for the selected shape.
Invalid example
<map name="floor-plan">
<area shape="poly" coords="0,0, 100,0,100,50" href="/reception" alt="Reception">
</map>
Valid example
<map name="floor-plan">
<area shape="poly" coords="0,0,100,0,100,50" href="/reception" alt="Reception">
</map>
The path in an <object> element's data attribute can't contain a literal space, so write each space as %20 or rename the file so its URL has none.
The data attribute holds the URL of the resource the object embeds, often a PDF, and the URL Standard doesn't allow spaces in a path. The validator reports values like /docs/annual report.pdf, and one stray space is enough, as in /docs/fees_ 2025.pdf. Accented letters in a file name are allowed, so only the space needs fixing.
Browsers percent-encode the space when they request the file, so the embedded document usually still shows. The markup is invalid anyway, and a tool that reads the raw value may not encode it. A space after the ? gives a different message, Illegal character in query, and %20 fixes that one too.
When the object has a fallback link to the same file, the link's href gets its own Illegal character in path segment error. Change both values together.
Invalid example
<object data="/docs/annual report.pdf" type="application/pdf" width="600" height="800">
<p><a href="/docs/annual report.pdf">Download the annual report (PDF)</a></p>
</object>
Valid example: space encoded as %20
<object data="/docs/annual%20report.pdf" type="application/pdf" width="600" height="800">
<p><a href="/docs/annual%20report.pdf">Download the annual report (PDF)</a></p>
</object>
Valid example: hyphen instead of a space
<object data="/docs/annual-report.pdf" type="application/pdf" width="600" height="800">
<p><a href="/docs/annual-report.pdf">Download the annual report (PDF)</a></p>
</object>
The decoding attribute of an <img> is set to something other than sync, async or auto.
decoding is a hint about when the browser should decode the image. async says the image can be decoded without delaying the rest of the content, sync asks for it to be decoded and shown together with the content around it, and auto, the default, leaves the choice to the browser. The value has to be one of those three lowercase keywords with nothing around it. A typo such as asynch, a keyword that belongs to another attribute such as lazy (that one goes in loading), uppercase ASYNC and an empty value all trigger this error.
When the reported value includes quotes, as in "async", the quotes are inside the value itself. That usually happens when the quotes around the value were HTML-encoded as ", often by a plugin or template that escaped markup a second time. " can't delimit an attribute, so the parser reads decoding="async" as an unquoted value and decodes both references into literal quotes. Wrapping the value in two sets of quotes, as in decoding='"async"', gives the same result. A value with only a trailing quote, such as async", means the opening quote is missing, and the validator also reports “"” in an unquoted attribute value.
Browsers treat an invalid value as auto, so the image still loads and displays, but the hint is lost. If you don't need a particular decoding mode, remove the attribute.
The " cause rarely affects decoding alone. If loading, width, height, srcset or sizes on the same <img> also report a stray ", fix the code that writes the tag instead of editing each attribute.
Invalid example
<img src="photo.jpg" alt="A red bicycle" width="600" height="400" decoding="async">
<img src="photo.jpg" alt="A red bicycle" width="600" height="400" decoding="asynch">
Valid example
<img src="photo.jpg" alt="A red bicycle" width="600" height="400" decoding="async">
The dir attribute on <table> only accepts ltr, rtl or auto, so a value such as none is invalid.
dir sets the base direction of the table. It affects the text inside each cell and also the column order: with rtl the first column sits on the right edge of the table, with ltr on the left. auto lets the browser pick the direction from the first strongly directional character in the content.
HTML has no keyword that switches direction off. A table without dir inherits the direction of its parent, usually set on the <html> element, which is probably what none was meant to do. Remove the attribute, or set ltr or rtl when the table needs a different direction from the rest of the page. An empty dir="" is invalid for the same reason.
Invalid example
<table dir="none">
<tr>
<th>Size</th>
<th>Price</th>
</tr>
<tr>
<td>Small</td>
<td>4.50</td>
</tr>
</table>
Valid example
<table>
<tr>
<th>Size</th>
<th>Price</th>
</tr>
<tr>
<td>Small</td>
<td>4.50</td>
</tr>
</table>
The fetchpriority attribute of an <img> is set to something other than high, low or auto.
fetchpriority is a hint about how soon the browser should fetch the image compared with the other images on the page. high suits the main image at the top of the page, low suits images that start out of view, such as the later slides of a carousel, and auto, the default, leaves the choice to the browser. The value has to be one of those three keywords with nothing around it. A made-up level such as highest, a keyword that belongs to another attribute such as eager (that one goes in loading) and an empty value all trigger this error.
When the reported value includes quotes, as in "high", the quotes are inside the value itself. That usually happens when the quotes around the value were HTML-encoded as ", often by a plugin or template that escaped markup a second time. " can't delimit an attribute, so the parser reads fetchpriority="high" as an unquoted value and decodes both references into literal quotes. Wrapping the value in two sets of quotes, as in fetchpriority='"high"', gives the same result. A value with only a trailing quote, such as high", means the opening quote is missing, and the validator also reports “"” in an unquoted attribute value.
Browsers treat an invalid value as auto, so the image still loads, but at the default priority. A hero image meant to be high can then start downloading later than intended. If you don't need a particular priority, remove the attribute.
The " cause rarely affects fetchpriority alone. If decoding, width, height, srcset or sizes on the same <img> also report a stray ", fix the code that writes the tag instead of editing each attribute.
Invalid example
<img src="hero.jpg" alt="A mountain lake at sunrise" width="1200" height="600" fetchpriority="high">
<img src="hero.jpg" alt="A mountain lake at sunrise" width="1200" height="600" fetchpriority="highest">
Valid example
<img src="hero.jpg" alt="A mountain lake at sunrise" width="1200" height="600" fetchpriority="high">
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.
<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 of an <img> has a literal " character inside its value, which may only contain digits.
height takes a non-negative integer such as 109, with no quotes, units or spaces. The quotes around an attribute value are HTML syntax, not part of the value. When an extra " ends up inside it, the validator stops at that character and rejects the value.
A value that starts with the quote, such as "109", usually comes from one of two mistakes. In the first, the quotes around the value were HTML-encoded as ", often by a plugin or template that escaped markup a second time. " can't delimit an attribute, so the parser reads height="109" as an unquoted value and decodes both references into literal quotes. In the second, the value is wrapped in two sets of quotes, as in height='"109"'.
A value that ends with the quote, such as 109", means the opening quote is missing (height=109") or the quotes don't match (height='109"'). Without an opening quote, the validator also reports “"” in an unquoted attribute value.
Browsers read 109" as 109, so that case often looks fine. A value that starts with the quote fails to parse, and the browser ignores height. Without it, the browser can't work out the image's aspect ratio before the file loads, so it reserves no vertical space and the content below shifts once the image arrives.
The cause rarely affects height alone. If width, srcset or sizes on the same <img> also report a stray ", fix the code that writes the tag instead of editing each attribute.
Invalid example
<img src="logo.png" alt="Company logo" width="600" height="109">
<img src="logo.png" alt="Company logo" width="600" height=109">
Valid example
<img src="logo.png" alt="Company logo" width="600" height="109">
The height attribute of an <object> takes a number of CSS pixels written as digits only, so a unit such as px in height="800px" makes the value invalid.
The HTML standard defines width and height on <object> as dimension attributes, whose value must be a valid non-negative integer. The validator reads the value from the left and stops at the first character that isn't a digit. In 800px that is the p, and pt or pc stop at the same letter. Other units are reported by their first character (v in 80vh, e in 40em). A percentage such as 100% gets its own error, about the % sign.
Browsers read the leading digits and drop the rest, so height="800px" already renders 800 pixels tall. Deleting the unit leaves the layout as it is. The attribute can only express pixels, though. For a height in viewport units, ems or a percentage of the container, remove the attribute and set the CSS height property instead.
Invalid example
<object data="/docs/annual-report.pdf" type="application/pdf" width="600" height="800px"></object>
Valid example
<object data="/docs/annual-report.pdf" type="application/pdf" width="600" height="800"></object>
To size the object relative to the viewport, use CSS:
<object data="/docs/annual-report.pdf" type="application/pdf" style="width: 100%; height: 80vh;"></object>
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
<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://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
<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>
The href value of a mailto: link contains a double quote ("), which the URL can only hold percent-encoded as %22.
The ilto: in the message is the tail of mailto:, and the check covers the whole address after it, so a " at the end of the address gets the same error as one at the start.
Usually the quotes were meant for the attribute and landed after the scheme, as in <a href=mailto:"info@example.com">. No quote follows the =, so HTML reads mailto:"info@example.com" as an unquoted value and copies both " characters into the URL. On that tag the validator also reports “"” in an unquoted attribute value. If a space follows the stray quote, the value ends at the space and the message shows only mailto:". Without a closing quote, a single " stays in front of the address.
Quoting the address on purpose doesn't help. In href='mailto:"info@example.com"' and href="mailto:"info@example.com"", the URL still starts with a literal " when the validator checks it. A display name in email header style, such as mailto:"Jane Doe" <info@example.com>, fails for the same reason: the URL holds only the address, and the name goes in the link text. For the rare address with a quoted local part, RFC 6068 asks for the quotes as %22, as in mailto:%22info%22@example.com.
Invalid example
<a href=mailto:"info@example.com">Email us</a>
Valid example
<a href="mailto:info@example.com">info@example.com</a>
A mailto: link in an href attribute contains a [ character that is not valid in a URI according to RFC 3986.
Square brackets ([ and ]) are reserved characters in URIs and must be percent-encoded when used outside their specific purpose (IPv6 address literals). If the email address or other part of the mailto: URI contains a [, it must be encoded as %5B. Similarly, ] must be encoded as %5D.
This error typically appears when a mailto: link includes square brackets in the subject, body, or display-related parts of the URL. Query parameters in mailto: links — such as ?subject= or ?body= — must have their values properly percent-encoded.
Invalid example
<a href="mailto:info@example.com?subject=[Action Required] Review">
Email us
</a>
Valid example
Percent-encode the square brackets in the query string:
<a href="mailto:info@example.com?subject=%5BAction%20Required%5D%20Review">
Email us
</a>
The [ becomes %5B, the ] becomes %5D, and spaces become %20. Most server side languages and JavaScript provide functions to handle this encoding automatically, such as encodeURIComponent() in JavaScript.
This tel: link has a double quote (") inside its href value, and " is not allowed in a URL.
The usual cause is quotes around the phone number instead of around the attribute value. In <a href=tel:"555-0100">, the value itself is unquoted, so the HTML parser keeps both quote marks as part of it and the URL becomes tel:"555-0100". For the same tag, the validator also reports “"” in an unquoted attribute value. Writing href='tel:"555-0100"', or using " entities, gives the same result, because " turns into a real " before the URL is checked.
For the number itself, RFC 3966 allows digits, a leading +, and the visual separators -, ., ( and ). Quote the whole attribute value and keep the number bare inside it. The link text can show the number in whatever format reads best. A value that holds two numbers has to become two links. Tel: with a capital T is fine, since scheme names are case-insensitive, though most pages write tel:.
Invalid example
<a href=tel:"+1-555-0100">Call us</a>
Valid example
<a href="tel:+1-555-0100">+1 555-0100</a>
Validate at scale.
Ship accessible websites, faster.
Automated HTML & accessibility validation for large sites. Check thousands of pages against WCAG guidelines and W3C standards in minutes, not days.
1K Weekly
1,000 monthly checks. Cancel anytime.
Get 1K Weekly →Join teams across 40+ countries