HTML Guides
Learn how to identify and fix common HTML validation errors flagged by the W3C Validator — so your pages are standards-compliant and render correctly across every browser. Also check our Accessibility Guides.
The HTML5 specification mandates UTF-8 as the only permitted character encoding for web documents declared via <meta> tags. Legacy encodings such as windows-1251 (a Cyrillic character encoding), iso-8859-1, shift_jis, and others are no longer valid values in HTML5 <meta> declarations. This restriction exists because UTF-8 is a universal encoding that can represent virtually every character from every writing system, eliminating the interoperability problems that plagued the web when dozens of competing encodings were in use.
When the validator encounters content="text/html; charset=windows-1251" on a <meta> element, it flags it because the charset= portion must be followed by utf-8 — no other value is accepted. This applies whether you use the longer <meta http-equiv="Content-Type"> syntax or the shorter <meta charset> syntax.
Why this matters
- Standards compliance: The WHATWG HTML Living Standard explicitly requires
utf-8as the character encoding when declared in a<meta>tag. Non-conforming encodings will trigger validation errors. - Internationalization: UTF-8 supports all Unicode characters, making your pages work correctly for users across all languages, including Cyrillic text that
windows-1251was originally designed for. - Security: Legacy encodings can introduce security vulnerabilities, including certain cross-site scripting (XSS) attack vectors that exploit encoding ambiguity.
- Browser consistency: While browsers may still recognize legacy encodings, relying on them can cause mojibake (garbled text) when there's a mismatch between the declared and actual encoding.
How to fix it
- Update the
<meta>tag to declareutf-8as the charset. - Re-save your file in UTF-8 encoding using your text editor or IDE. Most modern editors support this — look for an encoding option in "Save As" or in the status bar.
- Verify your server configuration. If your server sends a
Content-TypeHTTP header with a different encoding, the server header takes precedence over the<meta>tag. Make sure both agree on UTF-8. - Convert your content. If your text was originally written in
windows-1251, you may need to convert it to UTF-8. Tools likeiconvon the command line can help:iconv -f WINDOWS-1251 -t UTF-8 input.html > output.html.
Examples
❌ Incorrect: Using windows-1251 charset
<metahttp-equiv="Content-Type"content="text/html; charset=windows-1251">
✅ Correct: Using utf-8 with http-equiv syntax
<metahttp-equiv="Content-Type"content="text/html; charset=utf-8">
✅ Correct: Using the shorter <meta charset> syntax (preferred in HTML5)
<metacharset="utf-8">
Full document example
<!DOCTYPE html>
<htmllang="ru">
<head>
<metacharset="utf-8">
<title>Пример страницы</title>
</head>
<body>
<p>Текст на русском языке в кодировке UTF-8.</p>
</body>
</html>
The shorter <meta charset="utf-8"> syntax is generally preferred in HTML5 documents because it's more concise and achieves the same result. Whichever syntax you choose, place the charset declaration within the first 1024 bytes of your document — ideally as the very first element inside <head> — so browsers can detect the encoding as early as possible.
The HTML specification mandates that documents must be encoded in UTF-8. This requirement exists because UTF-8 is the universal character encoding that supports virtually every character from every writing system in the world. Older encodings like windows-1252, iso-8859-1, or shift_jis only support a limited subset of characters and can cause text to display incorrectly — showing garbled characters or question marks — especially for users in different locales or when content includes special symbols, accented letters, or emoji.
When the validator encounters charset=windows-1252 in your <meta> tag, it flags this as an error because the HTML living standard (WHATWG) explicitly states that the character encoding declaration must specify utf-8 as the encoding. This isn't just a stylistic preference — browsers and other tools rely on this declaration to correctly interpret the bytes in your document. Using a non-UTF-8 encoding can lead to security vulnerabilities (such as encoding-based XSS attacks) and accessibility issues when assistive technologies misinterpret characters.
To fix this issue, take two steps:
- Update the
<meta>tag to declareutf-8as the charset. - Re-save your file with UTF-8 encoding. Most modern code editors (VS Code, Sublime Text, etc.) let you choose the encoding when saving — look for an option like "Save with Encoding" or check the status bar for the current encoding. If your file was originally in
windows-1252, simply changing the<meta>tag without re-encoding the file could cause existing special characters to display incorrectly.
The HTML spec also recommends using the shorter <meta charset="utf-8"> form rather than the longer <meta http-equiv="Content-Type" ...> pragma directive, as it's simpler and achieves the same result. Either form is valid, but the charset declaration must appear within the first 1024 bytes of the document.
Examples
Incorrect: Using windows-1252 charset
<metahttp-equiv="Content-Type"content="text/html; charset=windows-1252">
This triggers the validator error because the charset is not utf-8.
Correct: Using the short charset declaration (recommended)
<metacharset="utf-8">
Correct: Using the http-equiv pragma directive with utf-8
<metahttp-equiv="Content-Type"content="text/html; charset=utf-8">
Full document example
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
Note that the <meta charset="utf-8"> tag should be the first element inside <head>, before any other elements (including <title>), so the browser knows the encoding before it starts parsing the rest of the document.
The textbox ARIA role identifies an element that allows free-form text input. While it can technically be applied to elements using contenteditable, it should not be placed on elements that already carry strong semantic meaning, such as <li>. A list item is expected to be a child of <ul>, <ol>, or <menu>, and its implicit listitem role communicates its purpose within a list structure to assistive technologies. Assigning role="textbox" to an <li> overrides this semantic, confusing screen readers and other assistive tools about whether the element is a list item or a text input field.
This is problematic for several reasons:
- Accessibility: Screen readers rely on roles to convey the purpose of elements to users. An
<li>withrole="textbox"sends mixed signals — it exists within a list structure but announces itself as a text input. - Standards compliance: The ARIA in HTML specification restricts which roles can be applied to specific elements. The
lielement does not allow thetextboxrole, which is why the W3C validator flags this as an error. - Browser behavior: Browsers may handle the conflicting semantics unpredictably, leading to inconsistent experiences across different user agents.
The best approach is to use native HTML form elements whenever possible. The <input type="text"> element handles single-line text input, and the <textarea> element handles multi-line input. These native elements come with built-in keyboard support, focus management, and form submission behavior — none of which you get for free with a role="textbox" on a non-form element.
If you genuinely need an editable area inside a list and cannot use native form elements, nest a <div> or <span> with role="textbox" inside the <li> rather than placing the role on the <li> itself.
Examples
❌ Incorrect: role="textbox" on an li element
<ul>
<lirole="textbox"contenteditable="true">Edit this item</li>
<lirole="textbox"contenteditable="true">Edit this item too</li>
</ul>
This triggers the validator error because textbox is not a valid role for <li>.
✅ Fix: Use native form elements
The simplest and most robust fix is to use standard form controls:
<ul>
<li>
<labelfor="item1">Item 1:</label>
<inputtype="text"id="item1"value="Edit this item">
</li>
<li>
<labelfor="item2">Item 2:</label>
<inputtype="text"id="item2"value="Edit this item too">
</li>
</ul>
For multi-line input, use <textarea>:
<ul>
<li>
<labelfor="note1">Note:</label>
<textareaid="note1">Edit this content</textarea>
</li>
</ul>
✅ Fix: Nest a div with role="textbox" inside the li
If you need a contenteditable area and cannot use native form elements, place the textbox role on a nested element:
<ul>
<li>
<divid="label1">Item 1:</div>
<div
role="textbox"
contenteditable="true"
aria-labelledby="label1"
aria-placeholder="Enter text">
Edit this item
</div>
</li>
</ul>
This preserves the <li> element's implicit listitem role while correctly assigning the textbox role to a semantically neutral <div>.
✅ Fix: Remove the list structure entirely
If the items aren't truly a list, consider dropping the <ul>/<li> structure altogether:
<divid="zipLabel">Enter your five-digit zipcode</div>
<div
role="textbox"
contenteditable="true"
aria-placeholder="5-digit zipcode"
aria-labelledby="zipLabel">
</div>
In every case, prefer native <input> and <textarea> elements over role="textbox" with contenteditable. Native elements provide accessible behavior by default, including keyboard interaction, form validation, and proper focus management, without requiring additional ARIA attributes or JavaScript.
The http-equiv attribute on the <meta> element does not accept "title" as a valid value.
The http-equiv attribute is designed to simulate specific HTTP response headers. It only accepts a limited set of predefined values: content-type, default-style, refresh, x-ua-compatible, and content-security-policy. Using "title" is not among these valid values and will trigger a validation error.
If you want to set the title of your page, use the <title> element inside <head> instead. The <title> element is the correct and standard way to define the document's title, which appears in the browser tab and is used by search engines.
Bad Example
<head>
<metahttp-equiv="title"content="My Page Title">
</head>
Good Example
<head>
<title>My Page Title</title>
</head>
In HTML, boolean attributes like allowfullscreen, disabled, readonly, and hidden work differently from what many developers expect. Their presence alone on an element means "true," and their absence means "false." The only valid values for a boolean attribute are the empty string ("") or the attribute's own name (e.g., allowfullscreen="allowfullscreen"). Setting a boolean attribute to "true" is not valid HTML according to the WHATWG HTML living standard, even though browsers will typically still interpret it as enabled.
This matters for several reasons. First, it violates the HTML specification and will produce a W3C validation error. Second, it can create confusion for other developers who may assume that setting the attribute to "false" would disable it — but that's not how boolean attributes work. Setting allowfullscreen="false" would still enable fullscreen because the attribute is present. Keeping your markup valid and semantically correct avoids these misunderstandings and ensures forward compatibility with browsers and tooling.
It's also worth noting that allowfullscreen is now considered a legacy attribute. The modern approach is to use the allow attribute with the "fullscreen" permission token, which is part of the broader Permissions Policy mechanism. The allow attribute gives you fine-grained control over multiple features in a single attribute.
Examples
Incorrect: boolean attribute set to "true"
This triggers the validation error because "true" is not a valid value for a boolean attribute.
<iframesrc="https://example.com"allowfullscreen="true"></iframe>
Correct: boolean attribute with no value
Simply include the attribute name without any value assignment.
<iframesrc="https://example.com"allowfullscreen></iframe>
Also correct: boolean attribute with an empty string
The empty string is a valid value for any boolean attribute.
<iframesrc="https://example.com"allowfullscreen=""></iframe>
Also correct: boolean attribute set to its own name
Per the spec, a boolean attribute can be set to a case-insensitive match of its own name.
<iframesrc="https://example.com"allowfullscreen="allowfullscreen"></iframe>
Recommended: using the modern allow attribute
The allow attribute is the preferred approach going forward. It replaces allowfullscreen and can also control other permissions like camera, microphone, and more.
<iframesrc="https://example.com"allow="fullscreen"></iframe>
You can combine multiple permissions in a single allow attribute:
<iframesrc="https://example.com"allow="fullscreen; camera; microphone"></iframe>
Common mistake: trying to disable with "false"
Be aware that the following does not disable fullscreen — the attribute is still present, so fullscreen is still allowed. This would also produce a validation error.
<!-- This does NOT disable fullscreen — and is invalid HTML -->
<iframesrc="https://example.com"allowfullscreen="false"></iframe>
To disable fullscreen, simply omit the attribute entirely:
<iframesrc="https://example.com"></iframe>
The aria-hidden attribute controls whether an element and its descendants are exposed to assistive technologies such as screen readers. When set to true, the element is hidden from the accessibility tree; when set to false, it remains visible. According to the WAI-ARIA specification, the only valid values for this attribute are the literal strings true and false. Any other value — including "true" with embedded quotation marks — is invalid.
When the validator reports a bad value like "true" (with the quotation marks as part of the value), it means the actual attribute value contains the characters "true" rather than just true. HTML attributes already use outer quotes as delimiters, so any quotes inside the value become part of the value itself. The browser or assistive technology may not recognize "true" as a valid ARIA state, which can lead to the element being incorrectly exposed to or hidden from screen readers, breaking the intended accessibility behavior.
This issue commonly arises in a few scenarios:
- Copy-pasting from formatted text where "smart quotes" or extra quoting gets included.
- Templating engines or frameworks that double-escape or double-quote attribute values (e.g.,
aria-hidden="{{value}}"where{{value}}already outputs"true"). - JavaScript that sets the attribute with extra quotes, such as
element.setAttribute("aria-hidden", '"true"').
To fix the issue, ensure the attribute value contains only the bare string true or false with no extra quotation marks, HTML entities, or escaped characters inside it.
Examples
Incorrect — extra quotes embedded in the value
<divaria-hidden='"true"'>
This content should be hidden from assistive tech
</div>
The rendered attribute value is literally "true" (five characters including the quotes), which is not a recognized ARIA value.
Incorrect — HTML entities producing extra quotes
<divaria-hidden=""true"">
This content should be hidden from assistive tech
</div>
The " entities resolve to quotation mark characters, producing the same invalid value of "true".
Correct — simple true value
<divaria-hidden="true">
This content is hidden from assistive tech
</div>
Correct — simple false value
<divaria-hidden="false">
This content is visible to assistive tech
</div>
Fixing the issue in JavaScript
If you're setting the attribute dynamically, make sure you aren't wrapping the value in extra quotes:
<divid="modal">Modal content</div>
<script>
// Incorrect:
// document.getElementById("modal").setAttribute("aria-hidden", '"true"');
// Correct:
document.getElementById("modal").setAttribute("aria-hidden","true");
</script>
Fixing the issue in templating engines
If a template variable already outputs a quoted string, don't add additional quotes around it. For example, in a templating system:
<!-- Incorrect: if myVar outputs "true" (with quotes) -->
<!-- <div aria-hidden="{{myVar}}"> -->
<!-- Correct: ensure myVar outputs just true (no quotes) -->
<divaria-hidden="true">
Content
</div>
The key takeaway is straightforward: the outer quotes in aria-hidden="true" are HTML syntax — they delimit the attribute value. The value itself must be exactly true or false with nothing extra. If you're generating HTML dynamically, inspect the rendered output in your browser's developer tools to confirm the attribute value doesn't contain stray quotation marks.
The defer attribute on the <script> element is a boolean attribute and should not be assigned a value like "true".
In HTML, boolean attributes don't work like regular attributes. A boolean attribute is considered "true" simply by being present on the element, and "false" by being omitted entirely. Setting defer="true" is invalid because the only allowed values for a boolean attribute are either the empty string ("") or the attribute's own name (e.g., defer="defer").
The defer attribute tells the browser to download the script in parallel with HTML parsing and execute it only after the document has been fully parsed. It only works on scripts with a src attribute — it has no effect on inline scripts.
Bad Example
<scriptsrc="app.js"defer="true"></script>
Good Example
<scriptsrc="app.js"defer></script>
This rule applies to all boolean attributes in HTML, such as disabled, checked, readonly, async, hidden, and others. Just include the attribute name by itself — no value needed.
In HTML, boolean attributes work differently than you might expect from programming languages. They don't accept "true" or "false" as values. Instead, the presence of the attribute means it's active, and its absence means it's inactive. This is defined in the WHATWG HTML Living Standard, which states that a boolean attribute's value must either be the empty string or a case-insensitive match for the attribute's name.
This means there are exactly three valid ways to write the disabled attribute:
disabled(no value)disabled=""(empty string)disabled="disabled"(attribute name as value)
Any other value — including "true", "false", "yes", "no", or "1" — is invalid and will trigger a W3C validation error.
A common and dangerous misunderstanding is that disabled="false" will make an element not disabled. It won't. Because boolean attributes are activated by their presence alone, disabled="false" still disables the element. The browser sees the disabled attribute is present and treats the element as disabled, completely ignoring the "false" value. This can lead to confusing bugs where elements appear permanently disabled.
This issue affects all elements that support the disabled attribute, including <input>, <button>, <select>, <textarea>, <fieldset>, <optgroup>, and <option>. The same rules apply to other boolean attributes like checked, readonly, required, autofocus, and hidden.
Why this matters
- Standards compliance: Using invalid attribute values violates the HTML specification and produces W3C validation errors.
- Maintainability: Developers reading
disabled="true"ordisabled="false"may misunderstand the intent, especially if they assume"false"removes the disabled state. - Framework pitfalls: Some JavaScript frameworks dynamically set
disabled="true"ordisabled="false"as string values. When the rendered HTML reaches the browser, both values result in a disabled element, which is rarely the intended behavior.
How to fix it
- To disable an element, add the
disabledattribute with no value, or usedisabled=""ordisabled="disabled". - To enable an element, remove the
disabledattribute entirely. Don't set it to"false". - In JavaScript, use the DOM property
element.disabled = trueorelement.disabled = false, or useelement.removeAttribute('disabled')to enable it. Avoidelement.setAttribute('disabled', 'false').
Examples
❌ Invalid: using "true" and "false" as values
<form>
<inputtype="text"disabled="true">
<selectdisabled="false">
<option>Option A</option>
</select>
<buttontype="submit"disabled="true">Submit</button>
</form>
Both the "true" and "false" values are invalid. Additionally, disabled="false" still disables the <select> element, which is almost certainly not what was intended.
✅ Valid: correct boolean attribute usage
<form>
<inputtype="text"disabled>
<select>
<option>Option A</option>
</select>
<buttontype="submit"disabled="disabled">Submit</button>
</form>
Here, the <input> and <button> are disabled using valid syntax. The <select> is enabled because the disabled attribute has been removed entirely.
✅ Valid: toggling disabled state with JavaScript
<form>
<inputtype="text"id="username"disabled>
<buttontype="button"id="toggle">Enable field</button>
<script>
document.getElementById('toggle').addEventListener('click',function(){
varfield=document.getElementById('username');
field.disabled=!field.disabled;
});
</script>
</form>
Using the disabled DOM property (a real boolean) is the correct way to toggle the disabled state dynamically. This avoids the pitfall of setting string values like "true" or "false" on the attribute.
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
<labelfor="colors">Select your favorite colors:</label>
<selectid="colors"name="colors"multiple="true">
<optionvalue="red">Red</option>
<optionvalue="green">Green</option>
<optionvalue="blue">Blue</option>
</select>
This triggers the validation error: Bad value "true" for attribute "multiple" on element "select".
✅ Fixed: attribute name only (preferred)
<labelfor="colors">Select your favorite colors:</label>
<selectid="colors"name="colors"multiple>
<optionvalue="red">Red</option>
<optionvalue="green">Green</option>
<optionvalue="blue">Blue</option>
</select>
✅ Fixed: empty string value
<labelfor="colors">Select your favorite colors:</label>
<selectid="colors"name="colors"multiple="">
<optionvalue="red">Red</option>
<optionvalue="green">Green</option>
<optionvalue="blue">Blue</option>
</select>
✅ Fixed: value matching the attribute name
<labelfor="colors">Select your favorite colors:</label>
<selectid="colors"name="colors"multiple="multiple">
<optionvalue="red">Red</option>
<optionvalue="green">Green</option>
<optionvalue="blue">Blue</option>
</select>
❌ Common mistake: using "false" to disable
<!-- This does NOT disable multiple selection — the attribute is still present -->
<selectname="colors"multiple="false">
<optionvalue="red">Red</option>
<optionvalue="green">Green</option>
</select>
✅ Correct way to disable: remove the attribute entirely
<labelfor="color">Select a color:</label>
<selectid="color"name="color">
<optionvalue="red">Red</option>
<optionvalue="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"
<selectname="color">
<optionselected="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)
<selectname="color">
<optionselected>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
<selectname="color">
<optionselected="">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
<selectname="color">
<optionselected="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
<selectname="color">
<optionselected="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:
<selectname="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 usingwrap="hard", you must also specify thecolsattribute so the browser knows where the wrap points are.
Since "virtual" was functionally identical to "soft", replacing it is straightforward. If you omit the wrap attribute altogether, the browser defaults to soft wrapping, which gives you the same behavior.
Why this matters
Using non-standard attribute values can lead to unpredictable behavior across browsers. While most modern browsers will likely fall back to soft wrapping when they encounter an unrecognized wrap value, this is not guaranteed by any specification. Sticking to valid values ensures consistent, cross-browser behavior and keeps your markup standards-compliant.
How to fix it
- Replace
wrap="virtual"withwrap="soft"for an explicit equivalent. - Remove the
wrapattribute entirely if you want the default soft wrapping behavior. - Use
wrap="hard"with acolsattribute if you actually need hard line breaks inserted on submission.
Examples
❌ Invalid: using the non-standard "virtual" value
<form>
<labelfor="msg">Message</label>
<textareaid="msg"name="msg"wrap="virtual"></textarea>
</form>
This triggers the error: Bad value "virtual" for attribute "wrap" on element "textarea".
✅ Fixed: using wrap="soft" (equivalent to "virtual")
<form>
<labelfor="msg">Message</label>
<textareaid="msg"name="msg"wrap="soft"></textarea>
</form>
✅ Fixed: omitting wrap entirely (defaults to "soft")
<form>
<labelfor="msg">Message</label>
<textareaid="msg"name="msg"></textarea>
</form>
Since "soft" is the default, removing the attribute produces identical behavior and cleaner markup.
✅ Fixed: using wrap="hard" with cols
If you need the submitted text to include line breaks at wrap points, use wrap="hard" and specify cols:
<form>
<labelfor="msg">Message</label>
<textareaid="msg"name="msg"wrap="hard"cols="60"rows="6"></textarea>
</form>
Note that cols is required when using wrap="hard". Omitting it will trigger a separate validation error.
Other legacy values to watch for
The value "virtual" isn't the only non-standard wrap value from the early web. You may also encounter wrap="physical" (the legacy equivalent of "hard") or wrap="off" (which disabled wrapping). Neither is valid in modern HTML. Replace "physical" with "hard" (and add cols), and replace "off" by removing the attribute and using CSS (white-space: nowrap; or overflow-wrap: normal;) to control visual wrapping if needed.
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
maxlengthattribute won't work because200 aria-required=is not a valid number. The browser cannot determine the intended character limit. - Lost attributes: The
aria-requiredattribute is swallowed into the malformedmaxlengthvalue, 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:
<inputtype="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:
<inputtype="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:
<inputtype="text"maxlength="50 class="username" placeholder="Enter name">
Correct — all quotes properly closed
<inputtype="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:
<linkrel="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:
<linkrel="stylesheet"media="only screen and (max-width: 768px)"href="mobile.css">
Incorrect: using deprecated min-device-width in a range
<linkrel="stylesheet"media="screen and (min-device-width: 768px) and (max-device-width: 1024px)"href="tablet.css">
Correct: using viewport-based equivalents
<linkrel="stylesheet"media="screen and (min-width: 768px) and (max-width: 1024px)"href="tablet.css">
Incorrect: using deprecated device-aspect-ratio
<linkrel="stylesheet"media="screen and (device-aspect-ratio: 16/9)"href="widescreen.css">
Correct: using aspect-ratio
<linkrel="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 */
@mediaonly screen and(max-device-width:768px){
.sidebar{display: none;}
}
/* Correct */
@mediaonly 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:
<linkrel="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:
<linkrel="stylesheet"media="only screen and (min-width: 768px)"href="tablet.css">
Incorrect: using max-device-width for a mobile breakpoint
<linkrel="stylesheet"media="screen and (max-device-width: 480px)"href="mobile.css">
Correct: using max-width
<linkrel="stylesheet"media="screen and (max-width: 480px)"href="mobile.css">
Incorrect: using device-aspect-ratio
<linkrel="stylesheet"media="screen and (device-aspect-ratio: 16/9)"href="widescreen.css">
Correct: using aspect-ratio
<linkrel="stylesheet"media="screen and (aspect-ratio: 16/9)"href="widescreen.css">
Incorrect: combining multiple deprecated features
<linkrel="stylesheet"media="screen and (min-device-width: 768px) and (max-device-width: 1024px)"href="tablet.css">
Correct: using viewport-based equivalents
<linkrel="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, orimage/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/*, orimage/*.
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
<inputtype="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
<inputtype="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
<inputtype="file"name="document"accept=".doc,.docx,.pdf">
Correct: using valid MIME types
<inputtype="file"name="document"accept="application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/pdf">
Correct: mixing MIME types, extensions, and wildcards
<inputtype="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
<inputtype="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
undefinedornullas 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><ahref="/step-1"aria-current="">Step 1</a></li>
<li><ahref="/step-2">Step 2</a></li>
</ol>
</nav>
❌ Invalid: custom keyword
Values like "active" or "yes" are not recognized:
<nav>
<ul>
<li><ahref="/"aria-current="active">Home</a></li>
<li><ahref="/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><ahref="/"aria-current="page">Home</a></li>
<li><ahref="/about">About</a></li>
</ul>
</nav>
✅ Fixed: using step for a multi-step process
<ol>
<li><ahref="/checkout/cart">Cart</a></li>
<li><ahref="/checkout/shipping"aria-current="step">Shipping</a></li>
<li><ahref="/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><ahref="/item-1"aria-current="true">Item 1</a></li>
<li><ahref="/item-2">Item 2</a></li>
<li><ahref="/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 -->
<ahref="/about"aria-current="false">About</a>
<!-- Preferred: just omit it -->
<ahref="/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 -->
<ahref="/"aria-current="page">Home</a>
<!-- What it should render for non-current pages (no attribute at all) -->
<ahref="/about">About</a>
The aria-expanded attribute only accepts the values "true", "false", or "undefined", and it belongs on interactive elements, not on generic div elements.
The aria-expanded attribute indicates whether a grouping element controlled by the current element is expanded or collapsed. Valid values are "true" (the controlled element is expanded), "false" (the controlled element is collapsed), and "undefined" (the element does not own or control a groupable element). Any other value, such as "yes", "no", "0", "1", or an empty string, is invalid.
Beyond the attribute value itself, aria-expanded is meant for interactive elements like button, a (with href), or elements with an appropriate ARIA role such as role="button". A plain div has no implicit interactivity, so placing aria-expanded on it without an interactive role triggers a validation warning.
To fix this, use a valid value and place the attribute on an appropriate element. If you must use a div, give it an interactive role and make it keyboard accessible with tabindex.
Examples
Invalid usage
<divaria-expanded="yes">
<p>Panel content</p>
</div>
Two problems here: the value "yes" is not valid, and a plain div is not an interactive element.
Valid usage
<buttonaria-expanded="false"aria-controls="panel1">
Toggle panel
</button>
<divid="panel1"hidden>
<p>Panel content</p>
</div>
The aria-expanded="false" attribute sits on a button, which is interactive by default, and the value is one of the allowed strings. When the panel opens, update the attribute to "true" and remove the hidden attribute via JavaScript.
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 -->
<inputtype="text"aria-required="">
<!-- "required" is not valid -->
<inputtype="email"aria-required="required">
✅ Correct usage with aria-required
<divid="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
<divid="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:
<labelfor="email">Email Address *</label>
<inputtype="email"id="email"name="email"required>
<labelfor="country">Country *</label>
<selectid="country"name="country"required>
<optionvalue="">Select a country</option>
<optionvalue="us">United States</option>
<optionvalue="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 -->
<scriptasync="true"src="app.js"></script>
<!-- Bad: "1" is not a valid boolean attribute value -->
<scriptasync="1"src="analytics.js"></script>
<!-- Bad: "yes" is not a valid boolean attribute value -->
<scriptasync="yes"src="tracker.js"></script>
<!-- Bad and misleading: this does NOT disable async -->
<scriptasync="false"src="app.js"></script>
✅ Valid: correct boolean attribute usage
<!-- Preferred: attribute name alone -->
<scriptasyncsrc="app.js"></script>
<!-- Also valid: empty string value -->
<scriptasync=""src="app.js"></script>
<!-- Also valid: value matching attribute name -->
<scriptasync="async"src="app.js"></script>
<!-- Correct way to disable async: remove the attribute -->
<scriptsrc="app.js"></script>
✅ Valid: async with module scripts
<scriptasynctype="module"src="app.mjs"></script>
<scriptasynctype="module">
import{init}from'./utils.mjs';
init();
</script>
This same rule applies to all boolean attributes in HTML, including defer, disabled, checked, required, hidden, and others. When in doubt, use the attribute name on its own with no value — it's the cleanest and most widely recognized form.
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
<inputtype="checkbox"checked="true">
<inputtype="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! -->
<inputtype="checkbox"checked="false">
Despite the value "false", the checkbox will still render as checked because the attribute is present.
Valid: attribute name only (recommended)
<inputtype="checkbox"checked>
<inputtype="radio"name="color"value="red"checked>
This is the most common and cleanest form.
Valid: empty string value
<inputtype="checkbox"checked="">
Valid: attribute name as its own value
<inputtype="checkbox"checked="checked">
This form is sometimes seen in XHTML-compatible markup.
Valid: omitting the attribute to leave unchecked
<inputtype="checkbox">
To represent an unchecked state, simply leave the attribute off.
Full example in context
<!DOCTYPE html>
<htmllang="en">
<head>
<title>Newsletter Preferences</title>
</head>
<body>
<form>
<fieldset>
<legend>Email preferences</legend>
<label>
<inputtype="checkbox"name="newsletter"checked>
Subscribe to newsletter
</label>
<label>
<inputtype="checkbox"name="promotions">
Receive promotional emails
</label>
</fieldset>
<fieldset>
<legend>Frequency</legend>
<label>
<inputtype="radio"name="frequency"value="daily"checked>
Daily
</label>
<label>
<inputtype="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/2024instead of2024-03-15 - Using informal date formats:
March 15, 2024or15-03-2024 - Omitting the
Tseparator between date and time:2024-03-15 14:30 - Using 12-hour time with AM/PM:
2:30 PMinstead of14:30 - Providing incomplete values:
2024-3-5instead of2024-03-05(months and days must be zero-padded)
Examples
Invalid: Slash-separated date
<timedatetime="03/15/2024">March 15, 2024</time>
Valid: ISO 8601 date format
<timedatetime="2024-03-15">March 15, 2024</time>
Invalid: Missing T separator and using AM/PM
<timedatetime="2024-03-15 2:30 PM">March 15 at 2:30 PM</time>
Valid: Date-time with T separator and 24-hour time
<timedatetime="2024-03-15T14:30">March 15 at 2:30 PM</time>
Invalid: Informal time string
<timedatetime="half past two">2:30 PM</time>
Valid: Simple time value
<timedatetime="14:30">2:30 PM</time>
Invalid: Non-standard duration
<timedatetime="1 hour 30 minutes">1.5 hours</time>
Valid: ISO 8601 duration
<timedatetime="PT1H30M">1.5 hours</time>
Valid: Date-time with timezone offset
<p>The event starts at <timedatetime="2024-03-15T14:30-05:00">2:30 PM ET on March 15</time>.</p>
Valid: Using only the month
<p>Published in <timedatetime="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
disabledattribute 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:
<inputtype="text"disabled="yes">
<inputtype="text"disabled="true">
<inputtype="text"disabled="false">
<inputtype="text"disabled="1">
<buttondisabled="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:
<inputtype="text"disabled>
<inputtype="text"disabled="">
<inputtype="text"disabled="disabled">
To have an enabled input, simply omit the attribute:
<inputtype="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>
<inputtype="text"id="username">
<buttontype="button"id="toggle">Toggle</button>
<script>
document.getElementById("toggle").addEventListener("click",function(){
varinput=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 -->
<inputtype="checkbox"checked="true">
<inputtype="email"required="required_field">
<!-- Correct -->
<inputtype="checkbox"checked>
<inputtype="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>
<labelfor="user name">Name</label>
<inputtype="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>
<labelfor="user_name">Name</label>
<inputtype="text"id="user_name">
</form>
Correct — using a hyphen instead of a space
<form>
<labelfor="user-name">Name</label>
<inputtype="text"id="user-name">
</form>
Correct — using camelCase
<form>
<labelfor="userName">Name</label>
<inputtype="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>
<labelfor=" email">Email</label>
<inputtype="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>
<labelfor="email">Email</label>
<inputtype="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
<inputtype="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"toheight="650". This is the simplest fix and keeps your sizing in the markup.Move sizing to CSS. Remove the
heightattribute entirely and use a stylesheet or inlinestyleattribute instead. This approach is more flexible because CSS supports units like%,em,vh, and more.
The same rule applies to the width attribute on <embed>, as well as height and width on elements like <img>, <video>, <iframe>, and <canvas> — all of which expect plain integers in HTML.
Examples
❌ Invalid: unit included in the HTML attribute
The px suffix causes the validator error because the attribute value must be digits only.
<embedsrc="file.pdf"type="application/pdf"width="800"height="650px">
Other invalid variations include:
<embedsrc="file.pdf"type="application/pdf"height="100%">
<embedsrc="file.pdf"type="application/pdf"height="40em">
<embedsrc="file.pdf"type="application/pdf"height="50vh">
✅ Fixed: plain integer without a unit
Remove the unit so the value is a valid non-negative integer.
<embedsrc="file.pdf"type="application/pdf"width="800"height="650">
✅ Fixed: sizing moved to CSS
If you need units other than pixels, or prefer to keep presentation in your stylesheets, use CSS instead of the HTML attribute.
<embedclass="pdf-viewer"src="file.pdf"type="application/pdf">
.pdf-viewer{
width:800px;
height:650px;
}
✅ Fixed: inline style as an alternative
You can also use the style attribute directly if a separate stylesheet isn't practical.
<embedsrc="file.pdf"type="application/pdf"style="width:800px;height:80vh;">
This is especially useful when you need viewport-relative or percentage-based sizing that HTML attributes can't express.
The 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"orheight="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
<iframewidth="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
<iframewidth="100%"height="100%"src="https://example.com/video"></iframe>
Percentage values are not allowed in the height attribute.
❌ Invalid: Decimal value
<iframewidth="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
<iframewidth="560"height=" 315 "src="https://example.com/video"></iframe>
The space before 315 is encountered where a digit is expected.
✅ Valid: Digits only
<iframewidth="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>
<iframeclass="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().
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