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 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
<divhiddenaria-hidden="true">
<p>This content is hidden from everyone.</p>
</div>
After fix
<divhidden>
<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
<formaction="/order">
<labelfor="city">City</label>
<inputid="city"name="city"type="text"aria-required="true"required>
<labelfor="email">Email</label>
<inputid="email"name="email"type="email"aria-required="true"required>
<labelfor="comments">Comments</label>
<textareaid="comments"name="comments"aria-required="true"required></textarea>
<buttontype="submit">Submit</button>
</form>
This triggers the validator warning on every element where both attributes appear.
✅ Using the native required attribute only
<formaction="/order">
<labelfor="city">City</label>
<inputid="city"name="city"type="text"required>
<labelfor="email">Email</label>
<inputid="email"name="email"type="email"required>
<labelfor="comments">Comments</label>
<textareaid="comments"name="comments"required></textarea>
<buttontype="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.
<divrole="combobox"aria-required="true"aria-expanded="false"aria-labelledby="color-label">
<spanid="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>
<inputtype="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>
<inputtype="checkbox"name="terms"> I agree to the terms
</label>
</form>
❌ Invalid: autocomplete on radio buttons
<form>
<fieldset>
<legend>Preferred contact method</legend>
<label>
<inputtype="radio"name="contact"value="email"autocomplete="off"> Email
</label>
<label>
<inputtype="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>
<inputtype="radio"name="contact"value="email"> Email
</label>
<label>
<inputtype="radio"name="contact"value="phone"> Phone
</label>
</fieldset>
</form>
❌ Invalid: autocomplete on a file input
<form>
<labelfor="resume">Upload resume:</label>
<inputtype="file"id="resume"name="resume"autocomplete="off">
</form>
✅ Fixed: remove autocomplete from the file input
<form>
<labelfor="resume">Upload resume:</label>
<inputtype="file"id="resume"name="resume">
</form>
✅ Valid: autocomplete on supported types
<form>
<labelfor="email">Email:</label>
<inputtype="email"id="email"name="email"autocomplete="email">
<labelfor="bday">Birthday:</label>
<inputtype="date"id="bday"name="bday"autocomplete="bday">
<labelfor="query">Search:</label>
<inputtype="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:
<formautocomplete="off">
<labelfor="username">Username:</label>
<inputtype="text"id="username"name="username">
<label>
<inputtype="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>
<htmllang="en">
<head>
<title>My Page</title>
<imgsrc="photo.jpg"alt="A smiling cat">
<metacharset="utf-8">
</head>
<body>
<p>Some content</p>
</body>
</html>
Correct — <meta charset> first, invalid elements moved to <body>
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>My Page</title>
</head>
<body>
<imgsrc="photo.jpg"alt="A smiling cat">
<p>Some content</p>
</body>
</html>
Incorrect — stray <div> in <head> breaks context
<!DOCTYPE html>
<htmllang="en">
<head>
<div>Oops</div>
<metacharset="utf-8">
<title>My Page</title>
</head>
<body>
<p>Hello</p>
</body>
</html>
Correct — only valid head elements before <meta charset>
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="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
<videosrc="video.mp4"controlscontrolslist="nodownload nofullscreen"></video>
The validator will report: Attribute "controlslist" not allowed on element "video" at this point.
✅ Valid: Removing the attribute
<videosrc="video.mp4"controls></video>
The simplest fix is to remove controlslist and accept the browser's default controls.
✅ Valid: Using custom controls with JavaScript
<videoid="my-video"src="video.mp4"></video>
<divclass="custom-controls">
<buttonid="play-btn">Play</button>
<inputid="seek-bar"type="range"min="0"max="100"value="0">
<buttonid="fullscreen-btn">Fullscreen</button>
</div>
<script>
constvideo=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
<videosrc="video.mp4"controlsdisableremoteplayback></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 -->
<audiosrc="song.mp3"controlscontrolslist="nodownload"></audio>
<!-- ✅ Valid -->
<audiosrc="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
<spancurrency="USD">49.99</span>
This triggers the validation error because currency is not a recognized attribute for <span>.
✅ Fixed: Using a data-currency attribute
<spandata-currency="USD">49.99</span>
The data-currency attribute is valid HTML. In JavaScript, you can access its value like this:
constspan=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:
<spanclass="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:
<spanitemscopeitemtype="https://schema.org/MonetaryAmount">
<metaitemprop="currency"content="USD">
<spanitemprop="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>
<spanclass="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
<spanclass="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:
<spanid="share-btn"data-displaytext="ShareThis">Share</span>
<script>
constbtn=document.getElementById('share-btn');
// Using getAttribute
consttext=btn.getAttribute('data-displaytext');
// Using the dataset API
consttextAlt=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:
<spanclass="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>
<tableheight="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
<tablestyle="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>
<tableclass="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
<ahref>Click here</a>
This triggers the validation warning. The attribute has no value, which is invalid for href.
❌ Incorrect: href with an empty string
<ahref="">Click here</a>
This resolves to the current page URL, likely causing an unintended page reload.
✅ Correct: Provide a real URL
<ahref="https://example.com">Visit Example</a>
✅ Correct: Use a fragment as a placeholder
<ahref="#"onclick="doSomething();returnfalse;">Perform action</a>
The return false prevents the page from scrolling to the top. A better modern approach uses event.preventDefault():
<ahref="#"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:
<buttontype="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:
<spanclass="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>
<htmllang="en">
<head>
<title>My Page</title>
</head>
<body>
<metahttp-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>
<htmllang="en">
<head>
<metahttp-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>
<htmllang="en">
<head>
<metacharset="UTF-8">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
✅ Correct: Content Security Policy in <head>
<head>
<metacharset="UTF-8">
<metahttp-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>
<htmlisolang="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>
<htmllang="pt">
<head>
<title>Minha Página</title>
</head>
<body>
<p>Olá, mundo!</p>
</body>
</html>
✅ Correct: Using a regional language subtag
<!DOCTYPE html>
<htmllang="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)
<h2id="chart-title">Monthly Sales</h2>
<svglabelledby="chart-title"role="img"viewBox="0 0 200 100">
<!-- chart content -->
</svg>
✅ Correct: Using aria-labelledby to reference an external heading
<h2id="chart-title">Monthly Sales</h2>
<svgaria-labelledby="chart-title"role="img"viewBox="0 0 200 100">
<!-- chart content -->
</svg>
✅ Correct: Using aria-labelledby to reference the SVG's own <title>
<svgaria-labelledby="icon-title"role="img"viewBox="0 0 24 24">
<titleid="icon-title">Search</title>
<pathd="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:
<h2id="section-title">Revenue</h2>
<pid="section-desc">Q1 2024 revenue by region</p>
<svgaria-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
<labelfor="quantity">Quantity</label>
<inputtype="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
<labelfor="quantity">Quantity</label>
<inputtype="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
<labelfor="start-date">Start date</label>
<inputtype="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
<labelfor="start-date">Start date</label>
<inputtype="date"id="start-date"name="start-date"min="2020-01-01"max="2030-12-31">
❌ Invalid: maxlength on a checkbox
<label>
<inputtype="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>
<inputtype="checkbox"name="agree"> I agree
</label>
✅ Valid: maxlength on supported text-based types
<labelfor="username">Username</label>
<inputtype="text"id="username"name="username"maxlength="30">
<labelfor="user-email">Email</label>
<inputtype="email"id="user-email"name="email"maxlength="254">
<labelfor="user-phone">Phone</label>
<inputtype="tel"id="user-phone"name="phone"maxlength="15">
<labelfor="site-url">Website</label>
<inputtype="url"id="site-url"name="website"maxlength="2048">
<labelfor="user-pass">Password</label>
<inputtype="password"id="user-pass"name="password"maxlength="128">
<labelfor="query">Search</label>
<inputtype="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.
<labelfor="age">Enter your age</label>
<inputtype="number"minlength="1"id="age">
✅ Fixed: Using min for number inputs
<labelfor="age">Enter your age</label>
<inputtype="number"min="1"id="age">
❌ Incorrect: minlength on a date input
<labelfor="start-date">Start date</label>
<inputtype="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.
<labelfor="start-date">Start date</label>
<inputtype="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:
<labelfor="username">Username (at least 3 characters)</label>
<inputtype="text"minlength="3"id="username">
<labelfor="email">Email</label>
<inputtype="email"minlength="5"id="email">
<labelfor="pass">Password (at least 8 characters)</label>
<inputtype="password"minlength="8"id="pass">
<labelfor="phone">Phone number</label>
<inputtype="tel"minlength="7"id="phone">
<labelfor="query">Search</label>
<inputtype="search"minlength="2"id="query">
<labelfor="website">Website URL</label>
<inputtype="url"minlength="10"id="website">
✅ Correct: minlength with maxlength on a textarea
<labelfor="bio">Bio (between 10 and 200 characters)</label>
<textareaminlength="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>
<htmllang="en">
<head>
<title>My Page</title>
<imgsrc="photo.jpg"alt="A smiling cat">
<metaname="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>
<htmllang="en">
<head>
<title>My Page</title>
<metaname="description"content="A page about cats">
</head>
<body>
<imgsrc="photo.jpg"alt="A smiling cat">
<p>Welcome!</p>
</body>
</html>
A <meta> tag accidentally placed in <body>
<!DOCTYPE html>
<htmllang="en">
<head>
<title>My Page</title>
</head>
<body>
<metaname="author"content="Jane Doe">
<p>Hello world</p>
</body>
</html>
Move the <meta> tag into <head>:
<!DOCTYPE html>
<htmllang="en">
<head>
<title>My Page</title>
<metaname="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>
<htmllang="en">
<head>
<title>My Page</title>
<linkrel="stylesheet"href="style.css"
<metaname="viewport"content="width=device-width, initial-scale=1">
</head>
<body>
<p>Content</p>
</body>
</html>
Close the <link> tag properly:
<!DOCTYPE html>
<htmllang="en">
<head>
<title>My Page</title>
<linkrel="stylesheet"href="style.css">
<metaname="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 -->
<imgsrc="photo.jpg"alt="smiling cat"</>
<!-- ✅ Fixed: removed the stray "<" -->
<imgsrc="photo.jpg"alt="smiling cat"/>
Stray < between attributes
<!-- ❌ Accidental "<" between attributes -->
<ahref="/about"<class="nav-link">About</a>
<!-- ✅ Fixed: removed the stray "<" -->
<ahref="/about"class="nav-link">About</a>
Fragment of another tag pasted inside an element
<!-- ❌ Leftover "<span" pasted inside the div's opening tag -->
<divclass="card"<span>
<p>Hello world</p>
</div>
<!-- ✅ Fixed: removed the pasted fragment -->
<divclass="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 -->
<spantitle="x < 10">Threshold</span>
<!-- ✅ Fixed: use the HTML entity -->
<spantitle="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
<divontouchstart="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
<divid="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
<buttonid="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
<divid="interactive-area"role="button"tabindex="0">Interact with me</div>
<script>
varel=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
<labelfor="qty">Quantity (multiples of 5):</label>
<inputtype="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
<labelfor="qty">Quantity (multiples of 5):</label>
<inputtype="number"id="qty"name="qty"min="0"max="100"step="5">
❌ Incorrect: pattern on a date input
<labelfor="dob">Date of birth:</label>
<inputtype="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
<labelfor="dob">Date of birth:</label>
<inputtype="date"id="dob"name="dob"min="1900-01-01"max="2025-12-31">
❌ Incorrect: pattern on a checkbox input
<label>
<inputtype="checkbox"name="agree"pattern=".+"> I agree to the terms
</label>
✅ Correct: using required instead of pattern for checkbox
<label>
<inputtype="checkbox"name="agree"required> I agree to the terms
</label>
✅ Correct: pattern on a supported text input
<labelfor="zip">ZIP code:</label>
<inputtype="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.
<inputtype="hidden"name="token"placeholder="Session token">
Fixed: Remove the placeholder attribute.
<inputtype="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>
<inputtype="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>
<inputtype="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.
<labelfor="birthday">Birthday</label>
<inputtype="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.
<labelfor="birthday">Birthday</label>
<inputtype="date"id="birthday"name="birthday">
<small>Format: YYYY-MM-DD</small>
Invalid: placeholder on a file input
<labelfor="upload">Upload</label>
<inputtype="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").
<labelfor="upload">Upload</label>
<inputtype="file"id="upload"name="upload">
Valid: placeholder on supported input types
These are all valid uses of the placeholder attribute:
<labelfor="email">Email</label>
<inputtype="email"id="email"name="email"placeholder="you@example.com">
<labelfor="phone">Phone</label>
<inputtype="tel"id="phone"name="phone"placeholder="+1 (555) 123-4567">
<labelfor="site">Website</label>
<inputtype="url"id="site"name="site"placeholder="https://example.com">
<labelfor="query">Search</label>
<inputtype="search"id="query"name="query"placeholder="Search articles…">
<labelfor="qty">Quantity</label>
<inputtype="number"id="qty"name="qty"placeholder="1">
<labelfor="pw">Password</label>
<inputtype="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
<inputtype="checkbox"name="agree"readonly>
Invalid: readonly on a range input
<inputtype="range"name="volume"min="0"max="100"value="50"readonly>
Invalid: readonly on a color input
<inputtype="color"name="theme"value="#ff0000"readonly>
Fixed: using readonly on a supported input type
<inputtype="text"name="code"value="ABC-123"readonly>
<inputtype="email"name="contact"value="user@example.com"readonly>
<inputtype="date"name="start"value="2024-01-15"readonly>
Fixed: using disabled for a non-textual input
<inputtype="checkbox"name="agree"checkeddisabled>
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 -->
<inputtype="hidden"name="agree"value="on">
<!-- The disabled checkbox is visible but non-interactive -->
<inputtype="checkbox"name="agree_display"checkeddisabled>
<labelfor="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:
<inputtype="checkbox"name="agree"checkedonclick="returnfalse;">
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>
<inputtype="hidden"name="token"required>
<buttontype="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>
<labelfor="volume">Volume:</label>
<inputtype="range"id="volume"name="volume"min="0"max="100"required>
<buttontype="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>
<labelfor="color">Pick a color:</label>
<inputtype="color"id="color"name="color"required>
<buttontype="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>
<inputtype="hidden"name="token"value="abc123">
<labelfor="volume">Volume:</label>
<inputtype="range"id="volume"name="volume"min="0"max="100">
<buttontype="submit">Submit</button>
</form>
✅ Valid: required on supported input types
<form>
<labelfor="email">Email:</label>
<inputtype="email"id="email"name="email"required>
<labelfor="dob">Date of birth:</label>
<inputtype="date"id="dob"name="dob"required>
<label>
<inputtype="checkbox"name="terms"required>
I agree to the terms
</label>
<buttontype="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.
The seamless attribute was originally drafted as part of the HTML5 specification to allow an <iframe> to appear as though its content were part of the containing document. When present, it was supposed to remove borders, inherit styles from the parent page, and allow the iframe content to participate in the parent document's styling context. However, no browser ever fully implemented the attribute, and it was officially removed from the WHATWG HTML Living Standard.
Because seamless is not a recognized attribute in the current HTML specification, using it triggers a validation error. Beyond validation, including it has no practical effect in any modern browser — it's simply ignored. Keeping unsupported attributes in your markup creates confusion for other developers who may assume the attribute is doing something meaningful. It also adds unnecessary clutter to your HTML.
How to Fix It
Remove the seamless attribute from any <iframe> elements. If you want to replicate the visual effects that seamless was intended to provide, you can use CSS:
- Remove the border: Apply
border: none;or use theframeborder="0"attribute (thoughframeborderis also obsolete — CSS is preferred). - Blend the background: Set
background: transparent;and add theallowtransparencyattribute if targeting older browsers. - Auto-resize to content: Use JavaScript to dynamically adjust the iframe's height based on its content (subject to same-origin restrictions).
Note that true seamless integration — where the iframe inherits parent styles and its content flows naturally within the parent document — is not achievable with standard iframe behavior. If you need that level of integration, consider including the content directly in the page, using a server-side include, or fetching the content with JavaScript via the fetch API and inserting it into the DOM.
Examples
❌ Invalid: Using the seamless attribute
<iframesrc="widget.html"seamless></iframe>
✅ Fixed: Attribute removed, CSS used for styling
<iframesrc="widget.html"style="border: none;"></iframe>
✅ Fixed: Using a CSS class for cleaner markup
<style>
.seamless-iframe{
border: none;
width:100%;
background: transparent;
}
</style>
<iframesrc="widget.html"class="seamless-iframe"title="Embedded widget"></iframe>
✅ Alternative: Embedding content directly instead of using an iframe
If the content is on the same origin and you need true seamless integration, consider loading it directly:
<divid="embedded-content">
<!-- Content loaded via server-side include or JavaScript fetch -->
</div>
HTML has a defined set of global attributes (such as id, class, lang, and title) and element-specific attributes. Any attribute that isn't part of these recognized sets will trigger a validation error. The st_title attribute is a proprietary, non-standard attribute that was used by older versions of the ShareThis sharing widget to pass metadata — specifically, the title of the content being shared.
The HTML5 specification introduced data-* attributes as the standard mechanism for embedding custom data on elements. These attributes allow developers to store arbitrary information without conflicting with the HTML spec. Newer versions of ShareThis and similar services have adopted this convention, but legacy code — especially in CMS themes, plugins, or modules — may still use the old non-standard format.
Using invalid attributes causes several problems:
- Standards compliance: The document fails W3C validation, which can indicate deeper markup quality issues.
- Future compatibility: Browsers are not required to handle non-standard attributes in any predictable way. Future browser updates could ignore or strip them.
- Maintainability: Non-standard attributes make code harder for other developers to understand and maintain.
- Accessibility tools: Screen readers and other assistive technologies rely on well-formed HTML. Invalid attributes can cause unexpected behavior in these tools.
To fix this, replace all proprietary ShareThis attributes with their data-* equivalents. For example, st_title becomes data-st-title, st_url becomes data-st-url, and displayText becomes data-st-displaytext. You should also update the ShareThis JavaScript library to a version that recognizes the new attribute format.
Examples
❌ Invalid: Using proprietary attributes
<spanclass="st_sharethis"st_title="My Article"st_url="https://example.com/article"displayText="ShareThis">
Share
</span>
This triggers validation errors for st_title, st_url, and displayText because none of these are valid HTML attributes.
✅ Valid: Using data-* attributes
<spanclass="st_sharethis"data-st-title="My Article"data-st-url="https://example.com/article"data-st-displaytext="ShareThis">
Share
</span>
All custom data is now stored in properly namespaced data-* attributes, which are fully compliant with the HTML5 specification.
✅ Valid: Using a button element with data-* attributes
If the element is interactive (e.g., it triggers a share action on click), consider using a <button> instead of a <span> for better accessibility:
<buttontype="button"class="st_sharethis"data-st-title="My Article"data-st-url="https://example.com/article">
Share this article
</button>
Fixing this in a CMS
If you're using Drupal, WordPress, or another CMS with a ShareThis module or plugin:
- Update the plugin/module to the latest version — most have already migrated to
data-*attributes. - Check your theme templates for hardcoded ShareThis markup that may still use the old attribute format.
- Search your codebase for
st_title,st_url, anddisplayTextand replace them withdata-st-title,data-st-url, anddata-st-displaytextrespectively. - Update the ShareThis JavaScript to a version compatible with the new attribute names, and verify that sharing functionality still works after the change.
HTML has a defined set of global attributes (like class, id, title, lang, etc.) and element-specific attributes that are permitted by the specification. Any attribute that falls outside this set — such as st_url, st_title, or displayText — will trigger a validation error because the browser and the validator don't recognize it as part of the HTML standard.
The st_url attribute, along with related attributes like st_title, st_via, and displayText, originated from early versions of the ShareThis social sharing library. These were proprietary attributes that the ShareThis JavaScript would read from the DOM to configure sharing behavior. While browsers generally ignore attributes they don't understand (so the page may still appear to work), using non-standard attributes violates the HTML specification and can cause several problems:
- Standards compliance: Invalid HTML can lead to unpredictable behavior across different browsers and future browser versions.
- Accessibility: Screen readers and other assistive technologies may not handle non-standard attributes correctly, potentially causing confusion.
- Maintainability: Non-standard markup is harder for other developers to understand and maintain.
- SEO: Search engine crawlers may penalize or misinterpret pages with significant validation errors.
HTML5 introduced data-* attributes specifically to solve this problem. Any custom data you need to attach to an element can use this pattern — for example, data-st-url instead of st_url. The ShareThis library itself updated its integration to use data-* attributes in later versions, so modern implementations should already follow this pattern.
How to Fix
- Replace proprietary attributes with
data-*equivalents: Convertst_urltodata-st-url,st_titletodata-st-title,displayTexttodata-display-text, and so on. - Update your ShareThis integration: If you're using an outdated version of ShareThis (or an outdated CMS module/plugin like an old Drupal integration), update to the latest version, which uses valid HTML5 attributes.
- Check your CMS templates: If the invalid attributes are hardcoded in a theme template or a content block, update them manually.
Examples
Invalid: Proprietary attributes on a <span>
<spanclass="st_facebook_large"displayText="Facebook"st_url="https://example.com"st_title="My Page Title"></span>
<spanclass="st_twitter_large"displayText="Twitter"st_url="https://example.com"st_title="My Page Title"></span>
This markup uses st_url, st_title, and displayText, none of which are valid HTML attributes.
Valid: Using data-* attributes instead
<spanclass="st_facebook_large"data-display-text="Facebook"data-st-url="https://example.com"data-st-title="My Page Title"></span>
<spanclass="st_twitter_large"data-display-text="Twitter"data-st-url="https://example.com"data-st-title="My Page Title"></span>
By prefixing each custom attribute with data-, the markup becomes valid HTML5. Note that you may also need to update the associated JavaScript to read from these new attribute names (e.g., using element.dataset.stUrl instead of element.getAttribute('st_url')).
Valid: Modern ShareThis integration
If you're updating your ShareThis integration entirely, the modern approach uses a different markup pattern:
<divclass="sharethis-inline-share-buttons"data-url="https://example.com"data-title="My Page Title"></div>
Modern versions of the ShareThis library expect data-* attributes by default, so upgrading the library and its associated markup is the cleanest solution. Check the ShareThis documentation for the latest recommended integration approach for your platform.
The type attribute is specific to the <input> element, where it determines the kind of input control rendered — such as text, checkbox, email, or number. The <textarea> element serves a single, distinct purpose: providing a multi-line plain-text editing area. Because it doesn't have multiple modes of operation, the HTML specification does not define a type attribute for it.
This error commonly occurs when developers confuse <textarea> with <input>, or when refactoring a single-line <input type="text"> into a multi-line <textarea> without removing the now-invalid type attribute. It can also appear when using templating systems or frameworks that apply attributes generically across different form elements.
While browsers will typically ignore the unrecognized type attribute on a <textarea>, keeping it in your markup causes W3C validation errors and can lead to confusion for other developers reading the code. Invalid attributes may also interfere with CSS attribute selectors (e.g., textarea[type="text"] is a selector targeting invalid markup), accessibility tools, or automated testing systems that rely on well-formed HTML.
To fix this issue, remove the type attribute from the <textarea> element. If you need to differentiate between multiple textareas, use id, name, or class attributes instead. If you actually need a single-line text input, use <input type="text"> rather than <textarea>.
Examples
Incorrect: type attribute on <textarea>
<labelfor="comment">Your comment:</label>
<textareatype="text"id="comment"name="comment"rows="4"cols="50"></textarea>
The type="text" attribute is not valid on <textarea> and will trigger a validation error.
Correct: <textarea> without type
<labelfor="comment">Your comment:</label>
<textareaid="comment"name="comment"rows="4"cols="50"></textarea>
Simply removing the type attribute resolves the issue. The <textarea> element inherently provides a multi-line text input, so no type is needed.
Correct: Using <input> when a single-line field is intended
If you intended a single-line text field, use <input> instead:
<labelfor="username">Username:</label>
<inputtype="text"id="username"name="username">
The type attribute is valid and expected on <input> elements, where it controls the type of input control rendered.
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