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.
An <li> element inside a <ul>, <ol>, or <menu> must not be assigned any ARIA role other than listitem.
When an <li> sits inside a standard list container (<ul>, <ol>, or <menu>) that has no explicit role attribute, the browser already treats the <li> as having an implicit role of listitem. Assigning a different role, such as role="presentation", role="option", or role="tab", contradicts the semantics of the parent-child relationship between the list and its items. Screen readers and other assistive technologies rely on this relationship to convey list structure to users.
The same restriction applies when the parent element has role="list" set explicitly. In that context, child <li> elements are still expected to carry only the listitem role.
If you need a different role on the items, restructure the markup. For example, use <div> elements instead of <ul> and <li>, and apply the appropriate roles directly. Or, if the items genuinely are list items, remove the conflicting role attribute.
Examples
Invalid: li with a non-listitem role inside a ul
<ul>
<li role="tab">Dashboard</li>
<li role="tab">Settings</li>
<li role="tab">Profile</li>
</ul>
Fixed: use appropriate elements when a different role is needed
If the items are tabs, drop the list markup and use elements that match the intended semantics:
<div role="tablist">
<button role="tab" aria-selected="true">Dashboard</button>
<button role="tab" aria-selected="false">Settings</button>
<button role="tab" aria-selected="false">Profile</button>
</div>
Fixed: keep the list but remove the conflicting role
If the content really is a list, remove the extra role:
<ul>
<li>Dashboard</li>
<li>Settings</li>
<li>Profile</li>
</ul>
The label element represents a caption for a form control. There are two ways to associate a label with its control:
- Implicit association — Place the form control directly inside the
labelelement. Noforattribute is needed because the browser automatically pairs the label with the nested control. - Explicit association — Use the
forattribute on thelabel, setting its value to theidof the target form control. The control doesn't need to be nested inside thelabelin this case.
Both methods are valid on their own. The problem occurs when you combine them incorrectly: you nest an input inside a label that has a for attribute, but the input either has no id or has an id that doesn't match the for value. This creates a contradiction — the for attribute points to a specific id, yet the nested input doesn't fulfill that reference. Browsers may handle this inconsistently, and assistive technologies like screen readers could fail to announce the label correctly, harming accessibility.
Why this matters
- Accessibility: Screen readers rely on the
for/idpairing to announce labels for form controls. A mismatched or missingidcan leave the control unlabeled for users who depend on assistive technology. - Standards compliance: The HTML specification requires that when a
forattribute is present, it must reference theidof a labelable element. A mismatch violates this rule. - Browser behavior: Some browsers will fall back to the implicit association when
fordoesn't resolve, but others may prioritize the broken explicit association, leaving the control effectively unlabeled.
How to fix it
You have two options:
- Remove the
forattribute if theinputis already nested inside thelabel. The implicit association is sufficient on its own. - Add or correct the
idon the nestedinputso it matches theforattribute value exactly.
Examples
❌ Nested input with no matching id
The for attribute says "email", but the input has no id at all:
<label for="email">
<input type="email" name="email">
</label>
❌ Nested input with a mismatched id
The for attribute says "email", but the input's id is "user-email":
<label for="email">
<input type="email" name="email" id="user-email">
</label>
✅ Fix by removing the for attribute (implicit association)
Since the input is nested inside the label, the association is automatic:
<label>
<input type="email" name="email">
</label>
✅ Fix by adding a matching id (explicit association)
The for value and the id value are identical:
<label for="email">
<input type="email" name="email" id="email">
</label>
✅ Fix by using explicit association without nesting
If you prefer to keep the for attribute, the input doesn't need to be nested at all:
<label for="email">Email</label>
<input type="email" name="email" id="email">
In most cases, choosing either implicit or explicit association — rather than mixing both — is the simplest way to avoid this error. If you do combine them, just make sure the for value and the id value match exactly.
The label element associates a caption with a form control. There are two ways to create this association:
- Implicit association — Place the form control directly inside the
labelelement. Nofororidattributes are needed. - Explicit association — Use the
forattribute on thelabel, setting its value to theidof the associated form control.
Both methods work independently. The problem arises when you mix them incorrectly — nesting a select inside a label that has a for attribute, but the select either has no id or has an id that doesn't match the for value. In this situation, the explicit association (via for) points to nothing or to the wrong element, while the implicit association (via nesting) still exists. This contradictory state violates the HTML specification and can cause assistive technologies like screen readers to misidentify or skip the label, reducing accessibility for users who rely on them.
The WHATWG HTML specification requires that when a for attribute is present on a label, it must reference a valid labelable element by id. If the form control is already nested inside the label, the for attribute is redundant — but if present, it still must correctly reference that control.
How to Fix
You have two options:
- Remove the
forattribute — If theselectis already inside thelabel, implicit association handles everything. This is the simplest fix. - Add or correct the
id— Keep theforattribute but ensure theselecthas a matchingid.
Examples
❌ Incorrect: for attribute with no matching id
The for attribute references "age", but the select has no id at all:
<label for="age">
Age
<select>
<option>Young</option>
<option>Old</option>
</select>
</label>
❌ Incorrect: for attribute with a mismatched id
The for attribute references "age", but the select has a different id:
<label for="age">
Age
<select id="age-select">
<option>Young</option>
<option>Old</option>
</select>
</label>
✅ Fix option 1: Remove the for attribute (implicit association)
Since the select is nested inside the label, implicit association is sufficient:
<label>
Age
<select>
<option>Young</option>
<option>Old</option>
</select>
</label>
✅ Fix option 2: Match the for attribute with the id (explicit association)
Keep the for attribute and give the select a matching id:
<label for="age">
Age
<select id="age">
<option>Young</option>
<option>Old</option>
</select>
</label>
✅ Fix option 3: Separate the label and select (explicit association only)
If you prefer to keep the select outside the label, explicit association with matching for and id is required:
<label for="age">Age</label>
<select id="age">
<option>Young</option>
<option>Old</option>
</select>
In most cases, option 1 (removing the for attribute) is the cleanest solution when the control is already nested. Use explicit association when the label and control are in separate parts of the DOM, such as in complex table or grid layouts.
The <article> element is meant to wrap independent, self-contained content such as blog posts, news stories, forum posts, product cards, or user comments. The W3C validator raises this warning because an <article> without a heading has no programmatic label, making it harder for users—especially those relying on screen readers—to understand what the article is about and to navigate between multiple articles on a page.
Screen readers often present a list of landmarks and headings to help users jump through a page's structure. When an <article> has no heading, it appears as an unlabeled region, which is confusing and defeats the purpose of using semantic HTML. A heading inside the <article> acts as its identifying title, giving both sighted users and assistive technology a clear summary of the content that follows.
This is a warning rather than a hard validation error, but it signals a real accessibility and usability concern. In nearly all cases, every <article> should contain a heading. The heading level you choose should fit logically within the document's heading hierarchy—typically <h2> if the <article> sits directly under the page's main <h1>, or <h3> if it's nested inside a section that already uses <h2>.
How to fix it
- Add a heading element (
<h2>–<h6>) as one of the first children inside each<article>. - Choose the correct heading level based on the document outline. Don't skip levels (e.g., jumping from
<h1>to<h4>). - Make the heading descriptive. It should clearly summarize the article's content.
If your design doesn't visually display a heading, you can use CSS to visually hide it while keeping it accessible to screen readers (see the examples below).
Examples
❌ Article without a heading (triggers the warning)
<h1>Latest news</h1>
<article>
<p>Our new product launched today with great success.</p>
</article>
<article>
<p>We are hiring frontend developers. Apply now!</p>
</article>
Each <article> here has no heading, so assistive technologies cannot identify them, and the validator raises a warning.
✅ Articles with proper headings
<h1>Latest news</h1>
<article>
<h2>Product launch a success</h2>
<p>Our new product launched today with great success.</p>
</article>
<article>
<h2>We're hiring frontend developers</h2>
<p>We are hiring frontend developers. Apply now!</p>
</article>
✅ Nested articles with correct heading hierarchy
<h1>Our blog</h1>
<article>
<h2>How to validate accessibility</h2>
<p>Use automated tools for an in-depth scan of your site.</p>
<section>
<h3>Comments</h3>
<article>
<h4>Comment by Alex</h4>
<p>Great article, very helpful!</p>
</article>
</section>
</article>
When articles are nested (e.g., comments inside a blog post), each level gets the next heading level to maintain a logical outline.
✅ Visually hidden heading for design flexibility
If your design doesn't call for a visible heading but you still need one for accessibility, use a CSS utility class to hide it visually while keeping it in the accessibility tree:
<style>
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
</style>
<article>
<h2 class="visually-hidden">Featured promotion</h2>
<p>Save 20% on all items this weekend only!</p>
</article>
This approach satisfies both the validator and assistive technologies without affecting your visual layout. Avoid using display: none or visibility: hidden, as those also hide the heading from screen readers.
The aria-hidden attribute is redundant when the hidden attribute is already present on an element.
The hidden attribute is a boolean HTML attribute that makes an element not visible and not perceivable to any user. Browsers hide the element from rendering, and accessibility tools also ignore it. The aria-hidden="true" attribute does something narrower: it removes the element from the accessibility tree but does not affect visual rendering.
When both attributes appear on the same element, aria-hidden adds nothing. The hidden attribute already tells assistive technologies to skip the element. The validator flags this as unnecessary markup.
There is one subtle difference worth knowing. aria-hidden="false" on an element with hidden would create a contradiction: the element is hidden from everyone visually, yet exposed to assistive technologies. This is almost never intentional and likely a bug.
If the goal is to hide content from all users, use hidden alone. If the goal is to hide content only from assistive technologies while keeping it visible, use aria-hidden="true" alone without hidden.
HTML examples
Before fix
<div hidden aria-hidden="true">
<p>This content is hidden from everyone.</p>
</div>
After fix
<div hidden>
<p>This content is hidden from everyone.</p>
</div>
The HTML required attribute was introduced in HTML5 and tells both the browser and assistive technologies that a form field must be filled in before the form can be submitted. The aria-required attribute serves the same purpose but comes from the WAI-ARIA specification, which was designed to fill accessibility gaps in HTML. Now that required is widely supported, using both attributes on the same element is unnecessary duplication.
The W3C validator raises this warning because redundant ARIA attributes add noise to your markup without providing any benefit. One of the core principles of ARIA usage is the first rule of ARIA: if you can use a native HTML element or attribute that already has the semantics you need, use that instead of adding ARIA. The native required attribute provides built-in browser validation, visual cues, and accessibility information all at once — something aria-required alone cannot do.
Modern screen readers such as NVDA, JAWS, and VoiceOver all correctly interpret the native required attribute and announce the field as required to users. Keeping both attributes doesn't cause a functional problem, but it creates maintenance overhead, clutters your code, and signals to other developers that something special might be going on when it isn't.
How to fix it
- If your element already has the
requiredattribute, removearia-required="true". - If your element only has
aria-required="true"and you want browser-native validation, replace it withrequired. - In rare cases where you need to support assistive technologies that don't recognize the native
requiredattribute (some very old screen reader versions), keeping both is a conscious trade-off — but for the vast majority of projects, the native attribute alone is sufficient.
Examples
❌ Redundant aria-required alongside required
<form action="/order">
<label for="city">City</label>
<input id="city" name="city" type="text" aria-required="true" required>
<label for="email">Email</label>
<input id="email" name="email" type="email" aria-required="true" required>
<label for="comments">Comments</label>
<textarea id="comments" name="comments" aria-required="true" required></textarea>
<button type="submit">Submit</button>
</form>
This triggers the validator warning on every element where both attributes appear.
✅ Using the native required attribute only
<form action="/order">
<label for="city">City</label>
<input id="city" name="city" type="text" required>
<label for="email">Email</label>
<input id="email" name="email" type="email" required>
<label for="comments">Comments</label>
<textarea id="comments" name="comments" required></textarea>
<button type="submit">Submit</button>
</form>
✅ Using aria-required on a non-native element
There are valid use cases for aria-required — specifically when you build custom form controls that don't use native form elements. In that scenario, aria-required is the correct choice because the native required attribute has no effect on non-form elements.
<div role="combobox" aria-required="true" aria-expanded="false" aria-labelledby="color-label">
<span id="color-label">Favorite color</span>
</div>
Here, aria-required="true" is necessary because a <div> doesn't support the native required attribute.
The autocomplete attribute tells the browser whether and how to automatically fill in a form field based on previously entered data. It makes sense for fields where users type or select values from a predictable set — like names, emails, addresses, dates, and numbers. However, certain input types don't produce text or numeric data that browsers could meaningfully store and recall. These include checkbox, radio, file, button, submit, reset, and image.
The HTML specification explicitly limits autocomplete to the following input types: color, date, datetime-local, email, hidden, month, number, password, range, search, tel, text, time, url, and week. Using it on any other type violates the spec and produces a validation error.
Why this matters
- Standards compliance: Browsers are not required to honor
autocompleteon unsupported input types, so including it has no practical effect and produces invalid markup. - Code clarity: Invalid attributes can confuse other developers reading your code, suggesting a behavior that doesn't actually exist.
- Accessibility: Screen readers and assistive technologies rely on valid markup to accurately convey form behavior to users. Unexpected attributes can introduce ambiguity.
How to fix it
- Identify the input type that has the
autocompleteattribute. - If the type is
checkbox,radio,file,button,submit,reset, orimage, remove theautocompleteattribute. - If you genuinely need autocomplete behavior, reconsider whether the correct input type is being used. For example, a field that should accept text input might have been incorrectly set to
type="checkbox".
Examples
❌ Invalid: autocomplete on a checkbox
<form>
<label>
<input type="checkbox" name="terms" autocomplete="off"> I agree to the terms
</label>
</form>
The browser cannot meaningfully autocomplete a checkbox, so this attribute is not allowed here.
✅ Fixed: remove autocomplete from the checkbox
<form>
<label>
<input type="checkbox" name="terms"> I agree to the terms
</label>
</form>
❌ Invalid: autocomplete on radio buttons
<form>
<fieldset>
<legend>Preferred contact method</legend>
<label>
<input type="radio" name="contact" value="email" autocomplete="off"> Email
</label>
<label>
<input type="radio" name="contact" value="phone" autocomplete="off"> Phone
</label>
</fieldset>
</form>
✅ Fixed: remove autocomplete from radio buttons
<form>
<fieldset>
<legend>Preferred contact method</legend>
<label>
<input type="radio" name="contact" value="email"> Email
</label>
<label>
<input type="radio" name="contact" value="phone"> Phone
</label>
</fieldset>
</form>
❌ Invalid: autocomplete on a file input
<form>
<label for="resume">Upload resume:</label>
<input type="file" id="resume" name="resume" autocomplete="off">
</form>
✅ Fixed: remove autocomplete from the file input
<form>
<label for="resume">Upload resume:</label>
<input type="file" id="resume" name="resume">
</form>
✅ Valid: autocomplete on supported types
<form>
<label for="email">Email:</label>
<input type="email" id="email" name="email" autocomplete="email">
<label for="bday">Birthday:</label>
<input type="date" id="bday" name="bday" autocomplete="bday">
<label for="query">Search:</label>
<input type="search" id="query" name="query" autocomplete="off">
</form>
If you want to disable autocomplete for an entire form — including checkboxes and other non-text fields — set autocomplete="off" on the <form> element itself rather than on individual inputs that don't support the attribute:
<form autocomplete="off">
<label for="username">Username:</label>
<input type="text" id="username" name="username">
<label>
<input type="checkbox" name="remember"> Remember me
</label>
</form>
The <meta charset="utf-8"> declaration is not only valid but recommended by the HTML living standard. So when the validator complains that the charset attribute is "not allowed at this point," the problem isn't the <meta> tag itself — it's what surrounds it. The HTML parser follows strict rules about which elements can appear inside <head>. When it encounters an element that doesn't belong there (like <img>, <div>, <p>, or other flow/phrasing content), it implicitly closes the <head> and opens the <body>. Any <meta> tags that come after that point are now parsed as being inside <body>, where <meta charset> is not permitted.
This is a problem for several reasons. First, the <meta charset> declaration must appear within the first 1024 bytes of the document so browsers can determine the character encoding early. If the parser moves it out of <head>, browsers may not apply the encoding correctly, potentially leading to garbled text — especially for non-ASCII characters. Second, this often signals a structural error in your HTML that could cause other unexpected rendering issues.
Common causes include:
- An element that only belongs in
<body>(like<img>,<div>,<span>, or<p>) placed before<meta charset>in the<head>. - A stray closing tag (like
</head>) appearing too early. - A
<script>tag with content that causes the parser to break out of<head>.
To fix the issue, inspect the elements that appear before <meta charset> in your <head>. Move any elements that don't belong in <head> into <body>, and place <meta charset="utf-8"> as the very first element inside <head>.
Examples
Incorrect — element before <meta> forces parser out of <head>
An <img> tag inside <head> causes the parser to implicitly close <head> and open <body>. The <meta charset> that follows is now parsed as being in <body>, triggering the error.
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
<img src="photo.jpg" alt="A smiling cat">
<meta charset="utf-8">
</head>
<body>
<p>Some content</p>
</body>
</html>
Correct — <meta charset> first, invalid elements moved to <body>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<img src="photo.jpg" alt="A smiling cat">
<p>Some content</p>
</body>
</html>
Incorrect — stray <div> in <head> breaks context
<!DOCTYPE html>
<html lang="en">
<head>
<div>Oops</div>
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<p>Hello</p>
</body>
</html>
Correct — only valid head elements before <meta charset>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<div>Content goes here</div>
<p>Hello</p>
</body>
</html>
Best practice
As a general rule, always make <meta charset="utf-8"> the very first child of <head>. This ensures the browser detects the encoding as early as possible and avoids the risk of other elements accidentally breaking the parser context before the charset is declared.
The controlslist attribute was proposed to give developers a way to hint to the browser which default media controls to show or hide. It accepts values like nodownload, nofullscreen, and noremoteplayback, allowing you to selectively disable specific buttons in the browser's built-in media player UI. For example, controlslist="nodownload" hides the download button on the video player.
However, this attribute was never adopted into the WHATWG HTML Living Standard or any W3C specification. It remains a Chromium-specific feature, meaning it only works in browsers like Chrome and Edge. Firefox, Safari, and other non-Chromium browsers simply ignore it. Because it's not part of any standard, the W3C HTML Validator rightfully reports it as an invalid attribute.
While using controlslist won't break your page — browsers that don't recognize it will silently ignore it — relying on non-standard attributes has downsides:
- Standards compliance: Your HTML won't validate, which can mask other real issues in validation reports.
- Browser compatibility: The behavior only works in Chromium-based browsers, giving an inconsistent experience across browsers.
- Future uncertainty: Non-standard attributes can be removed or changed without notice.
To fix this, you have a few options. The simplest is to remove the attribute entirely if the customization isn't critical. If you need fine-grained control over media player buttons, the most robust approach is to build custom media controls using JavaScript and the HTMLMediaElement API. For the specific case of disabling remote playback, there is a standardized attribute — disableremoteplayback — that you can use instead.
Examples
❌ Invalid: Using the non-standard controlslist attribute
<video src="video.mp4" controls controlslist="nodownload nofullscreen"></video>
The validator will report: Attribute "controlslist" not allowed on element "video" at this point.
✅ Valid: Removing the attribute
<video src="video.mp4" controls></video>
The simplest fix is to remove controlslist and accept the browser's default controls.
✅ Valid: Using custom controls with JavaScript
<video id="my-video" src="video.mp4"></video>
<div class="custom-controls">
<button id="play-btn">Play</button>
<input id="seek-bar" type="range" min="0" max="100" value="0">
<button id="fullscreen-btn">Fullscreen</button>
</div>
<script>
const video = document.getElementById("my-video");
document.getElementById("play-btn").addEventListener("click", () => {
video.paused ? video.play() : video.pause();
});
</script>
By omitting the controls attribute and building your own UI, you have full control over which buttons appear — across all browsers.
✅ Valid: Using disableremoteplayback for that specific need
<video src="video.mp4" controls disableremoteplayback></video>
If your goal was specifically controlslist="noremoteplayback", the standardized disableremoteplayback attribute achieves the same result and is valid HTML.
Audio element
The same issue and solutions apply to the <audio> element:
<!-- ❌ Invalid -->
<audio src="song.mp3" controls controlslist="nodownload"></audio>
<!-- ✅ Valid -->
<audio src="song.mp3" controls></audio>
The HTML specification defines a specific set of allowed attributes for each element. The <span> element supports global attributes (such as id, class, style, title, etc.) but does not recognize currency as a valid attribute. When you add a non-standard attribute like currency to an element, the W3C validator flags it because it doesn't conform to the HTML standard.
This pattern often appears in e-commerce sites, financial applications, or internationalization contexts where developers want to associate a currency code (like USD, EUR, or GBP) with a price displayed in a <span>. While browsers will typically ignore unrecognized attributes without breaking the page, using them creates several problems:
- Standards compliance: Invalid HTML can lead to unpredictable behavior across different browsers and future browser versions.
- Maintainability: Other developers (or tools) won't recognize non-standard attributes, making the codebase harder to understand and maintain.
- Accessibility: Assistive technologies rely on valid, well-structured HTML. Non-standard attributes may be ignored or misinterpreted.
- JavaScript interoperability: The
HTMLElement.datasetAPI is specifically designed to work withdata-*attributes, providing a clean and standard way to read custom data from elements.
The fix is straightforward: HTML provides the data-* attribute mechanism specifically for embedding custom data on elements. Any attribute prefixed with data- is valid on any HTML element, and its value is accessible in JavaScript via the element.dataset property.
Examples
❌ Invalid: Non-standard currency attribute
<span currency="USD">49.99</span>
This triggers the validation error because currency is not a recognized attribute for <span>.
✅ Fixed: Using a data-currency attribute
<span data-currency="USD">49.99</span>
The data-currency attribute is valid HTML. In JavaScript, you can access its value like this:
const span = document.querySelector('span');
console.log(span.dataset.currency); // "USD"
✅ Alternative: Using data-* with richer markup
If you need to convey more structured information, you can combine multiple data-* attributes:
<span class="price" data-currency="EUR" data-amount="29.99">€29.99</span>
✅ Alternative: Using microdata or structured markup
For SEO and machine-readable data, consider using established vocabularies like Schema.org:
<span itemscope itemtype="https://schema.org/MonetaryAmount">
<meta itemprop="currency" content="USD">
<span itemprop="value">49.99</span>
</span>
This approach provides semantic meaning that search engines and other consumers can understand, while remaining fully valid HTML.
The HTML specification defines a specific set of global attributes (like class, id, title, style, etc.) that are allowed on all elements, plus element-specific attributes for certain tags. Any attribute that isn't part of these standard sets and doesn't follow the data-* custom attribute convention is considered invalid and will trigger a validation error.
This issue is especially common with older integrations of third-party tools like ShareThis, AddThis, or similar social sharing widgets, particularly when embedded through a CMS like Drupal or WordPress. These older implementations relied on proprietary attributes such as displayText, st_url, and st_title directly on <span> or other elements. Modern versions of these tools have since migrated to valid data-* attributes.
Why This Matters
- Standards compliance: Non-standard attributes violate the HTML specification, meaning your markup is technically invalid and may behave unpredictably across browsers.
- Forward compatibility: Browsers could introduce a native
displaytextattribute in the future with entirely different behavior, causing conflicts with your code. - Accessibility: Assistive technologies rely on well-formed HTML. Non-standard attributes can confuse screen readers or other tools that parse the DOM.
- Maintainability: Using the standardized
data-*convention makes it immediately clear to other developers that an attribute holds custom data, improving code readability.
How to Fix It
- Identify all non-standard attributes on your elements (e.g.,
displayText,st_url,st_title). - Prefix each one with
data-to convert it into a valid custom data attribute (e.g.,data-displaytext,data-st-url,data-st-title). - Update any JavaScript that references these attributes. If JavaScript accesses them via
element.getAttribute('displayText'), change it toelement.getAttribute('data-displaytext')or use thedatasetAPI (element.dataset.displaytext). - If using a CMS or third-party plugin, update the plugin or module to its latest version, which likely uses valid
data-*attributes already.
Note that data-* attribute names should be all lowercase. Even if the original attribute used camelCase like displayText, the corrected version should be data-displaytext.
Examples
Invalid: Non-standard attribute on a <span>
<span class="st_sharethis" displaytext="ShareThis" st_url="https://example.com" st_title="My Page">
Share
</span>
This triggers validation errors for displaytext, st_url, and st_title because none of these are valid HTML attributes.
Valid: Using data-* attributes
<span class="st_sharethis" data-displaytext="ShareThis" data-st-url="https://example.com" data-st-title="My Page">
Share
</span>
Accessing data-* attributes in JavaScript
If your JavaScript relied on the old attribute names, update the references:
<span id="share-btn" data-displaytext="ShareThis">Share</span>
<script>
const btn = document.getElementById('share-btn');
// Using getAttribute
const text = btn.getAttribute('data-displaytext');
// Using the dataset API
const textAlt = btn.dataset.displaytext;
</script>
Valid: Using a standard attribute instead
In some cases, the intent of displaytext is simply to provide a label or tooltip. If so, a standard attribute like title may be more appropriate:
<span class="st_sharethis" title="ShareThis">Share</span>
Choose the approach that best matches your use case — data-* for custom data consumed by JavaScript, or a semantic HTML attribute if one already serves the purpose.
The <table> element in HTML supports a limited set of attributes — primarily global attributes like class, id, and style. The height attribute was never part of the HTML standard for tables (unlike width, which was valid in HTML 4 but has since been deprecated). Despite this, many browsers historically accepted height on <table> as a non-standard extension, which led to its widespread but incorrect use.
Because height is not a recognized attribute on <table>, using it means your markup is invalid and may behave inconsistently across browsers. Some browsers might honor it, others might ignore it entirely, and future browser versions could change their behavior at any time. Relying on non-standard attributes makes your code fragile and harder to maintain.
The fix is straightforward: remove the height attribute from the <table> element and use CSS to set the desired height. You can apply the CSS inline via the style attribute, or better yet, use an external or internal stylesheet with a class or ID selector.
Examples
❌ Invalid: height attribute on <table>
<table height="300">
<tr>
<td>Name</td>
<td>Score</td>
</tr>
<tr>
<td>Alice</td>
<td>95</td>
</tr>
</table>
This triggers the validator error: Attribute "height" not allowed on element "table" at this point.
✅ Fixed: Using inline CSS
<table style="height: 300px;">
<tr>
<td>Name</td>
<td>Score</td>
</tr>
<tr>
<td>Alice</td>
<td>95</td>
</tr>
</table>
✅ Fixed: Using a CSS class (preferred)
<style>
.tall-table {
height: 300px;
}
</style>
<table class="tall-table">
<tr>
<td>Name</td>
<td>Score</td>
</tr>
<tr>
<td>Alice</td>
<td>95</td>
</tr>
</table>
Using a class keeps your HTML clean and makes it easy to adjust the styling later without touching the markup. Note that min-height is often a better choice than height for tables, since table content can naturally grow beyond a fixed height, and min-height ensures the table is at least a certain size without clipping content.
Why This Happens
In HTML, certain attributes are boolean and can appear without a value (e.g., disabled, required). The href attribute is not one of them — it expects a valid URL, path, or fragment identifier as its value. When a validator encounters href with no value or an empty value, it raises a warning because the attribute is being used in a way that doesn't conform to the specification.
Writing <a href> produces a valueless attribute. Writing <a href=""> produces an attribute whose value is an empty string, which resolves to the current page's URL per the rules of relative URL resolution. Both forms are problematic:
hrefwithout a value: The HTML spec requireshrefto contain a valid URL. A valueless attribute is ambiguous and was actively dropped by IE7, meaning the element would lose its link behavior entirely in that browser.href="": While technically parsed as a relative URL pointing to the current document, this is almost never the developer's intention. It causes the page to reload or scroll to the top when clicked, which is confusing for users and harmful for accessibility.
The Impact
- Accessibility: Screen readers rely on the
hrefattribute to identify an<a>element as an interactive link. A missing or empty value creates confusion — the screen reader may announce it as a link but provide no destination, or may not treat it as a link at all. - Browser compatibility: As the validator message notes, IE7 would strip the attribute entirely, meaning the element lost its link semantics and styling. While IE7 is no longer in common use, valueless attributes can still cause edge-case issues in parsers, crawlers, and automated tools.
- Standards compliance: Valid HTML ensures consistent rendering and behavior across browsers, assistive technologies, and search engine crawlers.
How to Fix It
The fix depends on your intent:
- If the element should link somewhere, provide a real URL:
href="https://example.com"orhref="/about". - If the element is a placeholder link (e.g., during development or for JavaScript-driven actions), use
href="#"and prevent the default navigation with JavaScript. - If the element doesn't need to be a link at all, use a
<button>for interactive actions or a<span>for non-interactive text. This is often the most semantically correct choice.
Examples
❌ Incorrect: href without a value
<a href>Click here</a>
This triggers the validation warning. The attribute has no value, which is invalid for href.
❌ Incorrect: href with an empty string
<a href="">Click here</a>
This resolves to the current page URL, likely causing an unintended page reload.
✅ Correct: Provide a real URL
<a href="https://example.com">Visit Example</a>
✅ Correct: Use a fragment as a placeholder
<a href="#" onclick="doSomething(); return false;">Perform action</a>
The return false prevents the page from scrolling to the top. A better modern approach uses event.preventDefault():
<a href="#" id="action-link">Perform action</a>
<script>
document.getElementById("action-link").addEventListener("click", function (e) {
e.preventDefault();
doSomething();
});
</script>
✅ Correct: Use a <button> for actions
If the element triggers a JavaScript action rather than navigating somewhere, a <button> is the most semantically appropriate choice:
<button type="button" onclick="doSomething();">Perform action</button>
Buttons are natively focusable, keyboard-accessible, and clearly communicate interactive intent to assistive technologies — no href needed.
✅ Correct: Use a <span> for non-interactive text
If the element is purely visual and shouldn't be interactive at all:
<span class="label">Not a link</span>
This removes any expectation of interactivity for both the browser and the user.
The http-equiv attribute on a <meta> element simulates an HTTP response header, allowing you to define document-level metadata that would otherwise require server configuration. Because this metadata applies to the entire document and must be processed before the page content is rendered, the HTML specification requires that <meta http-equiv> elements appear within the <head> element. Placing them in the <body> is invalid and may cause browsers to ignore them entirely, leading to unexpected behavior like incorrect character encoding, broken content security policies, or missing refresh directives.
This error commonly occurs when:
- A
<meta http-equiv>tag is accidentally placed inside<body>. - Content is copy-pasted from one document into the body of another, bringing along
<meta>tags. - A templating system or CMS injects
<meta>tags in the wrong location.
Common http-equiv values
The http-equiv attribute supports several standard values:
content-type— Declares the document's MIME type and character encoding. In HTML5, the shorthand<meta charset="UTF-8">is preferred instead.refresh— Instructs the browser to reload the page or redirect after a specified number of seconds. Note that automatic refreshing is discouraged for accessibility reasons: it can disorient users, move focus back to the top of the page, and disrupt assistive technology. Avoid it unless absolutely necessary.content-security-policy— Defines a Content Security Policy for the document, helping prevent cross-site scripting (XSS) and other code injection attacks.default-style— Specifies the preferred stylesheet from a set of alternative stylesheets.
How to fix it
Find every <meta http-equiv> element in your document and ensure it is placed inside the <head> element, before any content in <body>. If the tag is duplicated or unnecessary, remove it.
Examples
❌ Incorrect: http-equiv inside <body>
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
</head>
<body>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<p>Hello, world!</p>
</body>
</html>
The <meta http-equiv> tag is inside <body>, which triggers the validation error.
✅ Correct: http-equiv inside <head>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
✅ Correct: using the HTML5 charset shorthand
In HTML5, you can replace <meta http-equiv="content-type" content="text/html; charset=UTF-8"> with the simpler charset attribute:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
✅ Correct: Content Security Policy in <head>
<head>
<meta charset="UTF-8">
<meta http-equiv="content-security-policy" content="default-src 'self'">
<title>Secure Page</title>
</head>
The content-security-policy value is particularly placement-sensitive — browsers will ignore it if it appears outside <head>, leaving your page without the intended security protections.
The W3C HTML Validator reports this error when it encounters isolang on the <html> element because isolang is not a recognized attribute in any version of HTML. This typically happens when developers attempt to specify the document's language but use an incorrect or made-up attribute name, possibly confusing it with ISO language code terminology.
The correct attribute for declaring a document's language is lang. This attribute accepts a valid BCP 47 language tag, which in most cases is a simple two-letter ISO 639-1 code (like en for English, fr for French, or pt for Portuguese). You can also use extended subtags for regional variants, such as en-US for American English or pt-BR for Brazilian Portuguese.
Setting the lang attribute properly is important for several reasons:
- Accessibility: Screen readers use the
langattribute to select the correct pronunciation rules, ensuring content is read aloud accurately. - SEO: Search engines use the language declaration to serve the right content to users based on their language preferences.
- Browser behavior: Browsers rely on
langfor features like spell-checking, hyphenation, and selecting appropriate default fonts for the given language. - Standards compliance: Only recognized attributes pass W3C validation, and valid markup ensures consistent, predictable behavior across browsers.
To fix this issue, simply replace isolang with lang on your <html> element. Keep the same language code value—it's the attribute name that's wrong, not the value.
Examples
❌ Incorrect: Using the invalid isolang attribute
<!DOCTYPE html>
<html isolang="pt">
<head>
<title>Minha Página</title>
</head>
<body>
<p>Olá, mundo!</p>
</body>
</html>
This triggers the error: Attribute "isolang" not allowed on element "html" at this point.
✅ Correct: Using the lang attribute
<!DOCTYPE html>
<html lang="pt">
<head>
<title>Minha Página</title>
</head>
<body>
<p>Olá, mundo!</p>
</body>
</html>
✅ Correct: Using a regional language subtag
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<title>Minha Página</title>
</head>
<body>
<p>Olá, mundo!</p>
</body>
</html>
Common language codes
Here are some frequently used ISO 639-1 language codes for the lang attribute:
en— Englishes— Spanishfr— Frenchde— Germanpt— Portuguesezh— Chineseja— Japanesear— Arabicko— Koreanru— Russian
The aria-labelledby attribute is part of the WAI-ARIA specification and provides an accessible name for an element by referencing the id values of other elements that contain the labeling text. Without the aria- prefix, labelledby is simply an unrecognized attribute that browsers and assistive technologies will ignore. This means your SVG graphic won't have the accessible label you intended, leaving screen reader users without a meaningful description of the content.
This issue is especially important for <svg> elements because SVG graphics are often used for icons, charts, and illustrations that need descriptive labels for accessibility. Using the incorrect attribute name means the graphic is effectively unlabeled for users who rely on assistive technology.
How to Fix It
Replace labelledby with aria-labelledby on your <svg> element. The attribute's value should be a space-separated list of one or more id values that reference elements containing the label text.
If you want to label an SVG using text that's already visible on the page, aria-labelledby is the ideal approach. You can also reference a <title> element inside the SVG itself.
Examples
❌ Incorrect: Using labelledby (invalid attribute)
<h2 id="chart-title">Monthly Sales</h2>
<svg labelledby="chart-title" role="img" viewBox="0 0 200 100">
<!-- chart content -->
</svg>
✅ Correct: Using aria-labelledby to reference an external heading
<h2 id="chart-title">Monthly Sales</h2>
<svg aria-labelledby="chart-title" role="img" viewBox="0 0 200 100">
<!-- chart content -->
</svg>
✅ Correct: Using aria-labelledby to reference the SVG's own <title>
<svg aria-labelledby="icon-title" role="img" viewBox="0 0 24 24">
<title id="icon-title">Search</title>
<path d="M15.5 14h-.79l-.28-.27A6.47 6.47 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5z"/>
</svg>
✅ Correct: Referencing multiple label sources
You can combine multiple id values to build a composite accessible name, separated by spaces:
<h2 id="section-title">Revenue</h2>
<p id="section-desc">Q1 2024 revenue by region</p>
<svg aria-labelledby="section-title section-desc" role="img" viewBox="0 0 400 200">
<!-- chart content -->
</svg>
In this case, a screen reader would announce something like "Revenue Q1 2024 revenue by region" as the accessible name for the SVG.
Tips
- When using
aria-labelledbyon<svg>, also addrole="img"to ensure consistent behavior across screen readers. - If the SVG is purely decorative, use
aria-hidden="true"instead of labeling it. - The
aria-labelledbyattribute overrides other labeling mechanisms likearia-labelor the<title>element, so use it when you want a specific label to take precedence.
The maxlength attribute provides built-in client-side validation that caps the number of characters a user can type into a field. Browsers enforce this by preventing further input once the limit is reached. However, this behavior only makes sense for input types that accept arbitrary text strings. Input types like number, date, range, and checkbox have their own value formats and constraints — a number input's value is controlled by min, max, and step, not by character count.
When you place maxlength on an unsupported input type, browsers will ignore the attribute. This means it provides no actual validation while giving a false sense of security. It also produces invalid HTML, which can cause issues with assistive technologies that may try to interpret the attribute and relay incorrect information to users. Keeping your markup valid ensures predictable behavior across browsers and a better experience for all users.
How to fix it
- Remove
maxlengthfrom any<input>whosetypeis notemail,password,search,tel,text, orurl. - Use the correct constraint attributes for the input type in question. For
numberinputs, useminandmax. Fordateinputs, useminandmaxwith date strings. - If you genuinely need character-length validation, consider whether a text-based input type is more appropriate for your use case, or implement the constraint in JavaScript.
Examples
❌ Invalid: maxlength on a number input
<label for="quantity">Quantity</label>
<input type="number" id="quantity" name="quantity" maxlength="3">
The number type does not support maxlength. Browsers will ignore it, and the HTML is invalid.
✅ Fixed: using min and max for a number input
<label for="quantity">Quantity</label>
<input type="number" id="quantity" name="quantity" min="0" max="999">
If the goal was to limit the value to three digits, min and max are the correct constraints.
❌ Invalid: maxlength on a date input
<label for="start-date">Start date</label>
<input type="date" id="start-date" name="start-date" maxlength="10">
Date inputs have a browser-provided date picker, and their values are always in YYYY-MM-DD format. The maxlength attribute has no effect here.
✅ Fixed: using min and max for a date input
<label for="start-date">Start date</label>
<input type="date" id="start-date" name="start-date" min="2020-01-01" max="2030-12-31">
❌ Invalid: maxlength on a checkbox
<label>
<input type="checkbox" name="agree" maxlength="1"> I agree
</label>
A checkbox is a boolean toggle — character length is meaningless here.
✅ Fixed: removing the invalid attribute
<label>
<input type="checkbox" name="agree"> I agree
</label>
✅ Valid: maxlength on supported text-based types
<label for="username">Username</label>
<input type="text" id="username" name="username" maxlength="30">
<label for="user-email">Email</label>
<input type="email" id="user-email" name="email" maxlength="254">
<label for="user-phone">Phone</label>
<input type="tel" id="user-phone" name="phone" maxlength="15">
<label for="site-url">Website</label>
<input type="url" id="site-url" name="website" maxlength="2048">
<label for="user-pass">Password</label>
<input type="password" id="user-pass" name="password" maxlength="128">
<label for="query">Search</label>
<input type="search" id="query" name="q" maxlength="100">
All six of these input types support maxlength because they accept free-form text where limiting character count is meaningful.
The minlength attribute defines the minimum number of characters (as UTF-16 code units) that a user can enter into a text-based input field. It provides built-in client-side validation without requiring JavaScript. However, it only makes sense on input types where the user is typing free-form text. For input types like number, date, color, range, or checkbox, the concept of a minimum character length doesn't apply — these inputs have their own constrained value formats or use other attributes like min and max for validation.
When the W3C validator encounters minlength on an unsupported input type, it flags the attribute as invalid. Browsers will typically ignore the attribute silently, meaning your intended validation won't actually work. This can lead to a false sense of security where you believe the input is being validated when it isn't.
The minlength value must be a non-negative integer (0 or higher) and must be less than or equal to maxlength if both are specified. The minlength attribute also works on <textarea> elements.
Examples
❌ Incorrect: minlength on a number input
The number input type doesn't support minlength. If you want to enforce a minimum value, use the min attribute instead.
<label for="age">Enter your age</label>
<input type="number" minlength="1" id="age">
✅ Fixed: Using min for number inputs
<label for="age">Enter your age</label>
<input type="number" min="1" id="age">
❌ Incorrect: minlength on a date input
<label for="start-date">Start date</label>
<input type="date" minlength="10" id="start-date">
✅ Fixed: Remove minlength from date inputs
Date inputs have a browser-controlled format, so character length constraints don't apply. Use min and max to constrain the date range.
<label for="start-date">Start date</label>
<input type="date" min="2024-01-01" id="start-date">
✅ Correct: minlength on supported input types
Here are valid uses of minlength across all supported input types:
<label for="username">Username (at least 3 characters)</label>
<input type="text" minlength="3" id="username">
<label for="email">Email</label>
<input type="email" minlength="5" id="email">
<label for="pass">Password (at least 8 characters)</label>
<input type="password" minlength="8" id="pass">
<label for="phone">Phone number</label>
<input type="tel" minlength="7" id="phone">
<label for="query">Search</label>
<input type="search" minlength="2" id="query">
<label for="website">Website URL</label>
<input type="url" minlength="10" id="website">
✅ Correct: minlength with maxlength on a textarea
<label for="bio">Bio (between 10 and 200 characters)</label>
<textarea minlength="10" maxlength="200" id="bio"></textarea>
Quick Reference
| Input Type | Supports minlength? | Alternative |
|---|---|---|
text, email, password, search, tel, url | ✅ Yes | — |
textarea | ✅ Yes | — |
number, range | ❌ No | Use min / max |
date, datetime-local, time, month, week | ❌ No | Use min / max |
checkbox, radio | ❌ No | Use required |
file | ❌ No | Validate with JavaScript |
color, hidden | ❌ No | Not applicable |
This error is misleading at first glance because the <meta> tag in question is often perfectly well-formed. The real problem is usually above the <meta> tag — an element that doesn't belong in <head> (such as <img>, <div>, <p>, or other flow content) has been placed there. When the HTML parser encounters such an element inside <head>, it implicitly closes the <head> and opens the <body>. From that point on, any subsequent <meta> tags are now technically inside the <body>, where the name attribute on <meta> is not permitted.
In other cases, the error can also occur when a <meta name="..."> tag is explicitly placed inside <body>, or when a typo or malformed tag earlier in the document breaks the expected document structure.
This matters for several reasons. Search engines and social media platforms rely on <meta> tags being in the <head> to extract page descriptions, Open Graph data, and other metadata. If the document structure is broken and <meta> tags end up in the <body>, this metadata may be ignored entirely. Additionally, elements like <img> inside <head> won't render as expected, and the overall document structure will be invalid, potentially causing unpredictable behavior across browsers.
How to fix it
- Look above the flagged
<meta>tag. Find any element in the<head>that doesn't belong there — common culprits include<img>,<div>,<span>,<p>,<a>, or<section>. - Move the offending element into the
<body>where it belongs. - If the
<meta>tag itself is in the<body>, move it into the<head>. - Check for malformed tags above the
<meta>— an unclosed tag or a typo can break the parser's understanding of the document structure.
Only certain elements are allowed inside <head>: <title>, <meta>, <link>, <style>, <script>, <noscript>, <base>, and <template>.
Examples
An invalid element in <head> breaks the context
The <img> tag is not allowed inside <head>. The parser implicitly closes <head> when it encounters it, so the <meta> tag that follows ends up in <body>:
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
<img src="photo.jpg" alt="A smiling cat">
<meta name="description" content="A page about cats">
</head>
<body>
<p>Welcome!</p>
</body>
</html>
Move the <img> into the <body> to fix the issue:
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
<meta name="description" content="A page about cats">
</head>
<body>
<img src="photo.jpg" alt="A smiling cat">
<p>Welcome!</p>
</body>
</html>
A <meta> tag accidentally placed in <body>
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
</head>
<body>
<meta name="author" content="Jane Doe">
<p>Hello world</p>
</body>
</html>
Move the <meta> tag into <head>:
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
<meta name="author" content="Jane Doe">
</head>
<body>
<p>Hello world</p>
</body>
</html>
A malformed tag disrupts the <head>
A missing closing > on a <link> tag can confuse the parser, causing subsequent elements to be misinterpreted:
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
<link rel="stylesheet" href="style.css"
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<p>Content</p>
</body>
</html>
Close the <link> tag properly:
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
<link rel="stylesheet" href="style.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<p>Content</p>
</body>
</html>
When the HTML parser encounters a < character inside an opening tag, it doesn't treat it as the start of a new tag — instead, it tries to interpret it as an attribute name. Since < is not a valid attribute name, the W3C validator raises this error. The browser may still render the page, but the behavior is undefined and can vary across different browsers, potentially leading to broken markup or elements that don't display correctly.
This issue most commonly occurs in a few scenarios:
- Accidental keystrokes — a stray
<typed while editing attributes. - Copy-paste artifacts — fragments of other tags getting pasted into the middle of an element.
- Misplaced angle brackets — attempting to nest or close tags incorrectly, such as adding
<before/>in a self-closing tag. - Template or code generation errors — dynamic HTML output that incorrectly injects
<into attribute positions.
Because this is a syntax-level problem, it can cause cascading parse errors. The parser may misinterpret everything after the stray < until it finds a matching >, which can swallow subsequent elements or attributes and produce unexpected rendering results.
How to Fix It
- Open the file referenced by the validator error and go to the indicated line number.
- Look inside the opening tag of the flagged element for a
<character that doesn't belong. - Remove the stray
<character. - If the
<was meant to represent a literal less-than sign in an attribute value, replace it with the HTML entity<.
Examples
Stray < before the closing slash
<!-- ❌ Stray "<" before the self-closing slash -->
<img src="photo.jpg" alt="smiling cat" < />
<!-- ✅ Fixed: removed the stray "<" -->
<img src="photo.jpg" alt="smiling cat" />
Stray < between attributes
<!-- ❌ Accidental "<" between attributes -->
<a href="/about" < class="nav-link">About</a>
<!-- ✅ Fixed: removed the stray "<" -->
<a href="/about" class="nav-link">About</a>
Fragment of another tag pasted inside an element
<!-- ❌ Leftover "<span" pasted inside the div's opening tag -->
<div class="card" <span>
<p>Hello world</p>
</div>
<!-- ✅ Fixed: removed the pasted fragment -->
<div class="card">
<p>Hello world</p>
</div>
Literal < intended in an attribute value
If you actually need a less-than sign inside an attribute value — for example, in a title or data-* attribute — use the < entity instead of a raw <.
<!-- ❌ Raw "<" in an attribute value can cause parsing issues -->
<span title="x < 10">Threshold</span>
<!-- ✅ Fixed: use the HTML entity -->
<span title="x < 10">Threshold</span>
The ontouchstart attribute is an inline event handler that some developers use to detect when a user touches an element on a touchscreen device. However, unlike standard event handler attributes such as onclick, onmousedown, or onkeydown, the ontouchstart attribute is not defined in the HTML specification. The W3C validator flags it because only attributes explicitly listed in the spec are allowed on a given element.
The touchstart event itself is a legitimate event defined in the Touch Events specification — it's the inline HTML attribute form (ontouchstart) that isn't recognized as a valid attribute by the HTML standard. This distinction is important: you can absolutely listen for touchstart events, just not via an inline attribute on an HTML element.
Why This Is a Problem
- Standards compliance: Using non-standard attributes produces invalid HTML, which can cause issues with automated testing, accessibility audits, and content management pipelines that enforce validation.
- Accessibility: Relying solely on touch events excludes users who navigate with a keyboard, mouse, or assistive technology. Touch-only handlers can make interactive elements completely inaccessible to non-touch users.
- Maintainability: Inline event handlers mix behavior with markup, making code harder to maintain and debug compared to centralized JavaScript event listeners.
- Browser inconsistencies: While most mobile browsers support the
touchstartevent, the inline attribute form is not guaranteed to work consistently across all environments.
How to Fix It
- Remove the inline
ontouchstartattribute from your HTML element. - Use
addEventListenerin JavaScript to attach thetouchstartevent listener programmatically. - Consider also handling
clickorpointerdownevents so the interaction works for all input types — mouse, keyboard, touch, and stylus. The Pointer Events API (pointerdown,pointerup, etc.) is a modern, unified approach that covers mouse, touch, and pen input in a single event model.
Examples
❌ Invalid: Inline ontouchstart attribute
<div ontouchstart="handleTouch()">Tap me</div>
This triggers the validation error because ontouchstart is not a recognized attribute in the HTML specification.
✅ Valid: Using addEventListener for touchstart
<div id="touch-target">Tap me</div>
<script>
document.getElementById("touch-target").addEventListener("touchstart", function(event) {
// Handle touch interaction
});
</script>
The touchstart event is attached through JavaScript, keeping the HTML valid and clean.
✅ Valid: Using Pointer Events for cross-input support
<button id="action-btn" type="button">Tap or click me</button>
<script>
document.getElementById("action-btn").addEventListener("pointerdown", function(event) {
// Handles mouse, touch, and pen input
});
</script>
Using pointerdown instead of touchstart provides a single handler that works across all input types. Note also the use of a <button> element, which is natively focusable and accessible, making it a better choice than a <div> for interactive elements.
✅ Valid: Supporting both touch and non-touch with separate listeners
<div id="interactive-area" role="button" tabindex="0">Interact with me</div>
<script>
var el = document.getElementById("interactive-area");
el.addEventListener("touchstart", function(event) {
// Handle touch-specific interaction
});
el.addEventListener("click", function(event) {
// Handle mouse and keyboard interaction
});
</script>
If you must use a <div> as an interactive element, add role="button" and tabindex="0" for accessibility, and attach both touchstart and click listeners to cover all input methods.
The pattern attribute provides a powerful way to add client-side form validation directly in HTML without relying on JavaScript. It accepts a regular expression that the browser uses to validate user input before the form is submitted. However, the HTML specification restricts pattern to input types where the user enters free-form text. Input types like number, date, range, color, and checkbox have their own built-in validation mechanisms (such as min, max, and step), so applying a regex pattern to them is meaningless and invalid.
When you add pattern to an unsupported input type, browsers will simply ignore it. This means you might think you have validation in place when you actually don't, which can lead to unexpected invalid data being submitted. Removing the invalid attribute also keeps your markup clean and standards-compliant, which benefits accessibility tools and future browser behavior.
Why certain types don't support pattern
number— Values are constrained bymin,max, andstep. The browser enforces numeric input natively.date,time,datetime-local,month,week— These use date/time pickers with their own format and range constraints.range— A slider control already constrained bymin,max, andstep.checkbox,radio— These are toggled on/off or selected from a group; a regex pattern doesn't apply.file— File selection is handled by the OS file picker; use theacceptattribute instead.color— Uses a color picker with a fixed hex format.hidden— Not user-editable, so client-side validation is irrelevant.
How to fix it
- Remove the
patternattribute if the input type already provides sufficient validation through its native controls. - Change the input
typeto one of the six supported types (email,password,search,tel,text, orurl) if you genuinely need regex-based validation. - Use alternative attributes like
min,max,step, oracceptthat are designed for the specific input type. - Use JavaScript validation if you need custom validation logic that goes beyond what native attributes offer.
Examples
❌ Incorrect: pattern on a number input
<label for="qty">Quantity (multiples of 5):</label>
<input type="number" id="qty" name="qty" pattern="[0-9]+" min="0" max="100">
The pattern attribute is not allowed on type="number". Since min, max, and step already handle numeric constraints, pattern is unnecessary here.
✅ Correct: using step instead of pattern for number validation
<label for="qty">Quantity (multiples of 5):</label>
<input type="number" id="qty" name="qty" min="0" max="100" step="5">
❌ Incorrect: pattern on a date input
<label for="dob">Date of birth:</label>
<input type="date" id="dob" name="dob" pattern="\d{4}-\d{2}-\d{2}">
The date input type already enforces a date format through its native picker, so pattern is invalid here.
✅ Correct: removing pattern from date input
<label for="dob">Date of birth:</label>
<input type="date" id="dob" name="dob" min="1900-01-01" max="2025-12-31">
❌ Incorrect: pattern on a checkbox input
<label>
<input type="checkbox" name="agree" pattern=".+"> I agree to the terms
</label>
✅ Correct: using required instead of pattern for checkbox
<label>
<input type="checkbox" name="agree" required> I agree to the terms
</label>
✅ Correct: pattern on a supported text input
<label for="zip">ZIP code:</label>
<input type="text" id="zip" name="zip" pattern="[0-9]{5}" title="Five digit ZIP code" required>
When using pattern on a supported input type, always include a title attribute that describes the expected format. Browsers display the title text as part of the validation error message, helping users understand what input is expected.
The placeholder attribute provides a short hint describing the expected value of an input field. This hint is displayed inside the control as light, greyed-out text when the field is empty and loses focus. It only makes sense on input types that present a visible text entry area where the user types characters directly. Input types like checkbox, radio, range, color, file, hidden, date, datetime-local, month, week, time, and image either don't display a text field at all or use a specialized UI widget (like a date picker or file selector), so the browser has nowhere to render placeholder text.
Using placeholder on an unsupported input type violates the HTML specification as defined by WHATWG. While browsers will typically just ignore the invalid attribute, it signals a likely mistake in your markup — perhaps the input type is wrong, or the hint should be conveyed differently (e.g., via a <label> or adjacent text). Keeping your HTML valid also improves maintainability, helps assistive technologies parse your page correctly, and prevents unexpected behavior if future browser versions handle invalid attributes differently.
It's also worth noting that even on supported input types, placeholder should not be used as a replacement for <label>. Placeholder text disappears as soon as the user starts typing, which can cause usability and accessibility issues. Always pair your inputs with a proper <label> element.
How to fix it
- Remove the
placeholderattribute if it's on an input type that doesn't support it. - Change the input type to one that supports
placeholderif the current type is incorrect. - Use a
<label>or visible helper text to convey the hint instead, especially for non-text input types.
Examples
Invalid: placeholder on a hidden input
A hidden input is never visible to the user, so a placeholder serves no purpose.
<input type="hidden" name="token" placeholder="Session token">
Fixed: Remove the placeholder attribute.
<input type="hidden" name="token">
Invalid: placeholder on a checkbox
Checkboxes don't have a text entry area, so there's nowhere for placeholder text to appear.
<label>
<input type="checkbox" name="agree" placeholder="Check to agree"> I agree
</label>
Fixed: Remove the placeholder and rely on the label text to convey the hint.
<label>
<input type="checkbox" name="agree"> I agree to the terms
</label>
Invalid: placeholder on a date input
Date inputs use a browser-provided date picker widget, not a free-text field.
<label for="birthday">Birthday</label>
<input type="date" id="birthday" name="birthday" placeholder="YYYY-MM-DD">
Fixed: Remove the placeholder. If you need to show a format hint, use a separate element.
<label for="birthday">Birthday</label>
<input type="date" id="birthday" name="birthday">
<small>Format: YYYY-MM-DD</small>
Invalid: placeholder on a file input
<label for="upload">Upload</label>
<input type="file" id="upload" name="upload" placeholder="Choose a file">
Fixed: Remove the placeholder. The browser provides its own label for file inputs (e.g., "No file chosen").
<label for="upload">Upload</label>
<input type="file" id="upload" name="upload">
Valid: placeholder on supported input types
These are all valid uses of the placeholder attribute:
<label for="email">Email</label>
<input type="email" id="email" name="email" placeholder="you@example.com">
<label for="phone">Phone</label>
<input type="tel" id="phone" name="phone" placeholder="+1 (555) 123-4567">
<label for="site">Website</label>
<input type="url" id="site" name="site" placeholder="https://example.com">
<label for="query">Search</label>
<input type="search" id="query" name="query" placeholder="Search articles…">
<label for="qty">Quantity</label>
<input type="number" id="qty" name="qty" placeholder="1">
<label for="pw">Password</label>
<input type="password" id="pw" name="pw" placeholder="Enter your password">
The HTML specification restricts the readonly attribute to input types where the user would normally type or select a textual/numeric/date value. The idea is straightforward: readonly means "you can see and select this value, but you can't edit it." That concept only makes sense for fields that contain editable text or structured data like dates and numbers. For controls like checkboxes, radio buttons, color pickers, file selectors, and range sliders, the interaction model is fundamentally different — there's no text to make "read-only."
The full list of input types that support readonly is:
textsearchurltelemailpassworddatemonthweektimedatetime-localnumber
The readonly attribute is not valid on these types: checkbox, radio, range, color, file, hidden, button, submit, reset, and image.
Why this matters
Standards compliance: Browsers are not required to honor readonly on unsupported input types. Even if a browser appears to respect it today, that behavior is not guaranteed and could change.
Form submission behavior: There's a critical difference between readonly and disabled. A readonly field's value is included in form submission data, while a disabled field's value is not. If you swap readonly for disabled to fix this error, be aware that the field's value won't be sent with the form unless you take additional steps (such as adding a hidden input).
Accessibility: Screen readers and assistive technologies rely on valid HTML to convey the correct state of form controls. Using readonly on an unsupported type can send confusing signals about the control's interactivity.
How to fix it
You have several options depending on your goal:
- Remove
readonlyif it was added by mistake or isn't necessary. - Change the input type to one that supports
readonly, if that fits your use case. - Use
disabledinstead to prevent interaction on non-textual inputs. Remember that disabled fields are excluded from form submission. - Pair
disabledwith a hidden input if you need the value submitted with the form but want the visible control to be non-interactive.
Examples
Invalid: readonly on a checkbox
<input type="checkbox" name="agree" readonly>
Invalid: readonly on a range input
<input type="range" name="volume" min="0" max="100" value="50" readonly>
Invalid: readonly on a color input
<input type="color" name="theme" value="#ff0000" readonly>
Fixed: using readonly on a supported input type
<input type="text" name="code" value="ABC-123" readonly>
<input type="email" name="contact" value="user@example.com" readonly>
<input type="date" name="start" value="2024-01-15" readonly>
Fixed: using disabled for a non-textual input
<input type="checkbox" name="agree" checked disabled>
Fixed: using disabled with a hidden input to preserve form submission
If you need the value to be submitted with the form while keeping the visible control non-interactive, pair a disabled control with a hidden input:
<!-- The hidden input ensures the value is submitted -->
<input type="hidden" name="agree" value="on">
<!-- The disabled checkbox is visible but non-interactive -->
<input type="checkbox" name="agree_display" checked disabled>
<label for="agree_display">I agree to the terms</label>
Fixed: using JavaScript to prevent changes (advanced)
If you truly need a checkbox that looks interactive but can't be changed, you can use JavaScript while keeping the HTML valid:
<input type="checkbox" name="agree" checked onclick="return false;">
Note that this approach relies on JavaScript and won't work if scripting is disabled. For most cases, using disabled (with or without a companion hidden input) is the simpler and more robust solution.
The required attribute is a boolean attribute that tells the browser a field must be filled in before the form can be submitted. However, not every input type supports this concept. Some input types always have a value (like range, which defaults to a midpoint, or color, which defaults to #000000), while others represent actions rather than user data (like submit, reset, image, and button). For hidden inputs, the user has no way to interact with the field at all, so requiring them to provide a value makes no sense.
The HTML specification explicitly limits required to the following input types: checkbox, date, datetime-local, email, file, month, number, password, radio, search, tel, text, time, url, and week.
Using required on an unsupported type is invalid HTML. Browsers will typically ignore the attribute in this situation, which means you might believe a field is required when it actually isn't being validated at all. This can lead to forms being submitted with missing or unexpected data. It also creates confusion for assistive technologies — screen readers may announce a field as required even though the browser won't enforce it, misleading users.
How to fix it
- Check the input type. If you're using
requiredon an input with a type likehidden,range,color,submit,reset,image, orbutton, the attribute is not allowed. - Remove the
requiredattribute if the input type inherently provides a value or doesn't accept user-provided data. - Change the input type if you actually need the field to be required and the current type doesn't match your intent.
- Use server-side validation for inputs like
hiddenthat can't userequiredbut still need a value.
Examples
❌ Invalid: required on a hidden input
<form>
<input type="hidden" name="token" required>
<button type="submit">Submit</button>
</form>
The user cannot interact with a hidden input, so required is not allowed here. The browser won't enforce it.
❌ Invalid: required on a range input
<form>
<label for="volume">Volume:</label>
<input type="range" id="volume" name="volume" min="0" max="100" required>
<button type="submit">Submit</button>
</form>
A range input always has a value (it defaults to the midpoint), so required is meaningless and not permitted.
❌ Invalid: required on a color input
<form>
<label for="color">Pick a color:</label>
<input type="color" id="color" name="color" required>
<button type="submit">Submit</button>
</form>
A color input always has a value (defaulting to #000000), so required is not valid here.
✅ Valid: required removed from unsupported types
<form>
<input type="hidden" name="token" value="abc123">
<label for="volume">Volume:</label>
<input type="range" id="volume" name="volume" min="0" max="100">
<button type="submit">Submit</button>
</form>
✅ Valid: required on supported input types
<form>
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
<label for="dob">Date of birth:</label>
<input type="date" id="dob" name="dob" required>
<label>
<input type="checkbox" name="terms" required>
I agree to the terms
</label>
<button type="submit">Submit</button>
</form>
These input types — email, date, and checkbox — all accept direct user input and are on the allowed list for the required attribute.
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