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 HTML specification defines the height attribute on media elements like <video> as accepting only a valid non-negative integer. This means the attribute value must consist solely of digits (e.g., "360"), with no units, keywords, or other characters. When you write height="auto", the validator expects to find a digit as the first character but encounters the letter "a", which produces the error.
The value "auto" is a valid concept in CSS, where height: auto tells the browser to calculate the element’s height automatically based on its intrinsic aspect ratio or content. However, HTML attributes and CSS properties follow different rules. The height HTML attribute is a simple pixel dimension hint — it doesn’t understand CSS keywords. Mixing CSS values into HTML attributes is a common mistake, and while browsers may silently ignore the invalid value, it leads to unpredictable behavior: the video may render without any height hint, potentially causing layout shifts as the browser discovers the video’s actual dimensions during loading.
Providing a valid height attribute matters for layout stability. When the browser knows the video’s dimensions before the media loads, it can reserve the correct amount of space in the page layout, preventing content from jumping around. This improves the user experience and contributes to better Core Web Vitals scores (specifically Cumulative Layout Shift). It also ensures your HTML is standards-compliant and accessible to assistive technologies that may rely on well-formed markup.
How to Fix
You have two approaches:
- Use a numeric value — Replace "auto" with an integer that represents the video’s height in pixels.
- Use CSS instead — Remove the height attribute from the HTML and apply height: auto (or any other value) via CSS. This is ideal when you want the video to scale responsively.
If you want the video to maintain its aspect ratio while scaling, the CSS approach is generally preferred for responsive designs. You can combine a width attribute (or CSS width) with CSS height: auto to let the browser calculate the correct height from the video’s intrinsic aspect ratio.
Examples
❌ Invalid: Using “auto” in the HTML attribute
<video width="640" height="auto" controls>
<source src="movie.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
This triggers the validation error because "auto" is not a non-negative integer.
✅ Fixed: Using a numeric height value
<video width="640" height="360" controls>
<source src="movie.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
The height attribute is now a valid integer. The browser reserves a 640×360 pixel area for the video before it loads.
✅ Fixed: Using CSS for responsive sizing
<video width="640" controls style="height: auto; max-width: 100%;">
<source src="movie.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
Here the height HTML attribute is removed entirely. CSS height: auto ensures the video scales proportionally, and max-width: 100% prevents it from overflowing its container. This is a common pattern for responsive video.
✅ Fixed: Using both attributes with CSS override
<video width="640" height="360" controls style="width: 100%; height: auto;">
<source src="movie.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
This approach provides the best of both worlds: the width and height HTML attributes give the browser an aspect ratio hint (preventing layout shifts), while the CSS makes the video responsive. Modern browsers use the attribute values to calculate the correct aspect ratio even when CSS overrides the actual rendered size.
The HTML specification defines the width and height attributes on <img> as accepting only valid non-negative integers — a sequence of one or more digits (0–9) with no letters, units, or symbols. These attributes tell the browser the intrinsic dimensions of the image in pixels, which helps it allocate the correct space in the layout before the image loads, preventing content layout shift (CLS).
When you set height="auto" or width="50%", the validator expects a digit as the first character but instead encounters a letter or symbol, producing the error: “Bad value ‘auto’ for attribute ‘height’ on element ‘img’: Expected a digit but saw ‘a’ instead.”
This matters for several reasons:
- Standards compliance: Browsers may silently ignore invalid attribute values, meaning your intended sizing won’t take effect and you’ll get default behavior without any visible warning to users.
- Layout stability: Valid width and height attributes allow the browser to calculate the image’s aspect ratio before it loads, reserving the correct amount of space and preventing layout shifts. Invalid values defeat this mechanism.
- Predictability: Relying on browser error recovery for invalid markup leads to inconsistent behavior across different browsers and versions.
To fix this, you have two options:
- Use plain integers in the width and height attributes to specify pixel dimensions (e.g., width="600" height="400").
- Use CSS for any non-pixel or dynamic sizing like auto, percentages, max-width, viewport units, etc.
A best practice is to set the width and height attributes to the image’s actual intrinsic pixel dimensions (to preserve aspect ratio and prevent layout shift), then use CSS to control the rendered size responsively.
Examples
Invalid: using “auto” or units in attributes
<!-- "auto" is not a valid integer -->
<img src="photo.jpg" alt="A cat sitting on a windowsill" height="auto" width="auto">
<!-- Percentage is not a valid integer -->
<img src="banner.jpg" alt="Site banner" width="100%">
<!-- Units like "px" are not allowed -->
<img src="icon.png" alt="Settings icon" width="32px" height="32px">
Valid: using plain integers in attributes
<!-- Correct: plain integers representing pixels -->
<img src="photo.jpg" alt="A cat sitting on a windowsill" width="800" height="600">
<img src="icon.png" alt="Settings icon" width="32" height="32">
Valid: using CSS for responsive or dynamic sizing
When you need behavior like auto, percentages, or max-width, use CSS instead:
<!-- Use attributes for intrinsic size, CSS for responsive behavior -->
<img
src="photo.jpg"
alt="A cat sitting on a windowsill"
width="800"
height="600"
style="max-width: 100%; height: auto;">
This approach gives you the best of both worlds: the browser knows the image’s aspect ratio from the attributes (preventing layout shift), while CSS ensures it scales responsively within its container.
Valid: using a CSS class for reusability
<style>
.responsive-img {
max-width: 100%;
height: auto;
}
</style>
<img
src="photo.jpg"
alt="A cat sitting on a windowsill"
width="800"
height="600"
class="responsive-img">
The sizes attribute works together with the srcset attribute to help the browser choose the most appropriate image source for the current viewport and layout. Its value must follow a specific syntax: a comma-separated list where each entry is an optional media condition followed by a CSS length value. The final entry acts as a default and should be a length value without a media condition.
The value "auto" is not recognized as a valid CSS length or source size descriptor by the current HTML specification. While there is a newer sizes="auto" feature being developed for use specifically with loading="lazy" images, it is not yet universally part of the validated standard, and the W3C validator flags it as invalid. When the validator encounters auto, it expects a number (like 100vw or 50px) or a minus sign, but instead finds the letter “a”, producing this error.
Why This Matters
- Standards compliance: Using invalid attribute values means your HTML doesn’t conform to the specification, which could lead to unpredictable behavior across browsers.
- Responsive image loading: When sizes contains an invalid value, browsers may fall back to the default value of 100vw, which can cause them to download unnecessarily large images, hurting performance.
- Accessibility and tooling: Invalid HTML can cause issues with assistive technologies and automated tools that rely on well-formed markup.
How to Fix It
You have several options depending on your use case:
- Specify explicit sizes — Provide a valid source size list that describes how wide the image will be displayed at various viewport widths.
- Use a simple default — Set sizes to a single CSS length like "100vw" if the image always spans the full viewport width.
- Remove sizes entirely — If you don’t use srcset with width descriptors, the sizes attribute is unnecessary and can be removed.
- Use sizes="auto" with loading="lazy" — If you intentionally want the browser to determine sizes automatically for lazy-loaded images, be aware this is a newer feature that the validator may not yet support. You can suppress this specific warning if you’ve confirmed browser support meets your needs.
Examples
❌ Invalid: Using auto as the sizes value
<img
src="photo.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
sizes="auto"
alt="A scenic mountain view">
This triggers the error because auto is not a valid CSS length or source size descriptor.
✅ Fixed: Using a responsive source size list
<img
src="photo.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
sizes="(max-width: 600px) 100vw, (max-width: 1000px) 50vw, 33vw"
alt="A scenic mountain view">
This tells the browser: use 100% of the viewport width on screens up to 600px, 50% on screens up to 1000px, and 33% on larger screens. The browser then picks the best image from srcset.
✅ Fixed: Using a simple default size
<img
src="photo.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
sizes="100vw"
alt="A scenic mountain view">
If the image always fills the full viewport width, 100vw is a straightforward valid value.
✅ Fixed: Removing sizes when srcset is not used
<img src="photo.jpg" alt="A scenic mountain view">
If you’re not using srcset with width descriptors, the sizes attribute serves no purpose and can be safely removed. Without it, the browser defaults to 100vw when interpreting any srcset width descriptors.
✅ Fixed: Using a fixed pixel width
<img
src="photo.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w"
sizes="400px"
alt="A scenic mountain view">
If the image is always displayed at a fixed width regardless of viewport size, you can specify that width directly as a CSS length value.
The HTML specification defines the width attribute on <video> (and <img>, <canvas>, etc.) as a “valid non-negative integer,” which means it must consist only of digits like "640" or "1280". Values like "auto", "100%", or "50vw" are not permitted in the HTML attribute itself — these are CSS concepts, not valid HTML attribute values.
This matters for several reasons. First, browsers use the width and height HTML attributes to reserve the correct amount of space in the layout before the video loads, which prevents content layout shift (CLS). When the value is invalid, the browser may ignore it entirely, leading to layout jumps as the page loads. Second, invalid attributes can cause unpredictable rendering behavior across different browsers. Third, standards compliance ensures your markup is future-proof and works reliably with assistive technologies.
A common reason developers set width="auto" is to make the video responsive. The correct way to achieve this is through CSS rather than through the HTML attribute. You can still set width and height attributes with valid integers to define the video’s intrinsic aspect ratio (which helps the browser reserve space), and then override the display size with CSS.
How to Fix
- Replace "auto" with a valid integer that represents the desired pixel width.
- If you need responsive sizing, remove the width attribute or keep it for aspect ratio hinting, and use CSS to control the rendered size.
Examples
❌ Invalid: Using "auto" as the width attribute
<video width="auto" height="360" controls>
<source src="video.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
This triggers the error because "auto" is not a non-negative integer.
✅ Fixed: Specifying a valid pixel value
<video width="640" height="360" controls>
<source src="video.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
The width and height attributes use plain integers — no units, no keywords. The browser interprets these as pixels.
✅ Fixed: Responsive video using CSS
If you want the video to scale fluidly with its container, use CSS instead of the HTML attribute:
<style>
.responsive-video {
width: 100%;
height: auto;
}
</style>
<video class="responsive-video" controls>
<source src="video.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
In CSS, width: 100% and height: auto are perfectly valid and will make the video scale to fill its container while maintaining its aspect ratio.
✅ Best practice: Combine HTML attributes with CSS
For the best of both worlds — layout stability and responsive sizing — provide width and height attributes for aspect ratio hinting, then override with CSS:
<style>
.responsive-video {
max-width: 100%;
height: auto;
}
</style>
<video class="responsive-video" width="640" height="360" controls>
<source src="video.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
Here, the width="640" and height="360" attributes tell the browser the video’s intrinsic 16:9 aspect ratio, so it can reserve the right amount of space before the video loads. The CSS max-width: 100% ensures the video never exceeds its container, and height: auto keeps the aspect ratio intact. This approach minimizes layout shift while remaining fully responsive.
The itemscope attribute is part of the HTML Microdata specification, used to define the scope of structured data on a page. It works alongside itemtype and itemprop to provide machine-readable metadata about your content, which search engines and other tools can use to better understand your pages.
In HTML, boolean attributes follow a specific rule: their mere presence on an element represents a true state, and their absence represents false. Unlike JavaScript or other programming languages where you might write itemscope="true" or itemscope="false", HTML boolean attributes do not work this way. The only valid forms for a boolean attribute are:
- The attribute name alone: itemscope
- The attribute with an empty value: itemscope=""
- The attribute with its own name as the value: itemscope="itemscope"
Assigning any other value — including "true" or "false" — is invalid HTML. This is especially confusing because itemscope="false" does not disable the attribute. Since the attribute is still present on the element, the browser treats it as active. This can lead to incorrect structured data being generated, which may cause search engines to misinterpret your content.
This issue matters for several reasons:
- Standards compliance: Invalid attribute values violate the HTML specification, causing W3C validation errors.
- Structured data accuracy: Incorrect microdata markup can result in search engines misreading your page content, potentially affecting rich search results.
- Developer intent: Writing itemscope="false" suggests you want to disable the attribute, but it actually does the opposite — the attribute remains active.
To fix this, simply use the bare attribute name when you want it enabled, or remove it entirely when you don’t.
Examples
Incorrect: assigning "true" to itemscope
<html itemscope="true" itemtype="https://schema.org/WebPage">
<!-- ... -->
</html>
Incorrect: assigning "false" to itemscope
This does not disable itemscope — the attribute is still present, so the browser treats it as active.
<div itemscope="false" itemtype="https://schema.org/Product">
<span itemprop="name">Widget</span>
</div>
Correct: using the bare attribute
<html itemscope itemtype="https://schema.org/WebPage">
<!-- ... -->
</html>
Correct: using an empty string value
This is an equally valid way to specify a boolean attribute, though the bare form is more common and readable.
<div itemscope="" itemtype="https://schema.org/Product">
<span itemprop="name">Widget</span>
</div>
Correct: removing the attribute entirely
If you don’t need itemscope on the element, simply omit it.
<html lang="en">
<head>
<title>My Page</title>
</head>
<body>
<p>No microdata here.</p>
</body>
</html>
Correct: a complete example with microdata
<!DOCTYPE html>
<html lang="en" itemscope itemtype="https://schema.org/WebPage">
<head>
<title>My Product</title>
</head>
<body>
<div itemscope itemtype="https://schema.org/Product">
<h1 itemprop="name">Super Widget</h1>
<p itemprop="description">The best widget money can buy.</p>
</div>
</body>
</html>
This same rule applies to all HTML boolean attributes, such as hidden, disabled, checked, required, readonly, autoplay, and defer. None of them accept "true" or "false" as values — they are either present or absent.
The ARIA in HTML specification defines which roles are allowed on each HTML element. Heading elements (<h1>–<h6>) have an implicit role of heading, and the set of roles they can be explicitly assigned is limited. The button role is not among them, so applying role="button" directly to a heading element is invalid.
This matters for several reasons. First, headings play a critical role in document structure and accessibility — screen reader users rely on headings to navigate and understand the page hierarchy. Assigning role="button" to a heading overrides its semantic meaning, which confuses assistive technologies. Second, browsers and screen readers may handle conflicting semantics unpredictably, leading to an inconsistent experience for users. Third, it violates the W3C HTML specification, which means your markup won’t pass validation.
Why This Combination Is Problematic
When you apply role="button" to an element, assistive technologies treat it as an interactive button. This completely replaces the element’s native heading semantics. Users who navigate by headings would no longer find that heading in their list, and users who navigate by interactive controls would encounter a “button” that may not behave like one (lacking keyboard support, focus management, etc.).
If you genuinely need something that looks like a heading but acts as a button, there are valid approaches to achieve this without breaking semantics.
How to Fix It
There are several strategies depending on your intent:
-
Use a <button> element styled as a heading. This is often the cleanest approach when the primary purpose is interactivity. You can style the button with CSS to match your heading appearance.
-
Wrap the heading in a <div> with role="button". This preserves the heading in the document outline while making the wrapper interactive. However, be aware that the button role applies role="presentation" to all descendant elements, meaning assistive technologies will strip the heading semantics from the <h2> inside it. The text content remains accessible, but it won’t be recognized as a heading.
-
Place a <button> inside the heading. This keeps the heading semantics intact for document structure while making the text inside it interactive. This pattern is commonly used for accordion-style components and is the approach recommended by the WAI-ARIA Authoring Practices.
Examples
❌ Invalid: role="button" on a heading
<h2 role="button">Toggle Section</h2>
This triggers the validation error because button is not an allowed role for heading elements.
✅ Fix: Use a <button> styled as a heading
<button type="button" class="heading-style">Toggle Section</button>
.heading-style {
font-size: 1.5em;
font-weight: bold;
background: none;
border: none;
cursor: pointer;
}
✅ Fix: Wrap the heading in a container with role="button"
<div role="button" tabindex="0">
<h2>Toggle Section</h2>
</div>
Note that this approach causes the <h2> to lose its heading semantics for assistive technologies, since the button role does not support semantic children. Also remember to add tabindex="0" so the element is keyboard-focusable, and implement keydown handlers for Enter and Space to replicate native button behavior.
✅ Fix: Place a <button> inside the heading (recommended for accordions)
<h2>
<button type="button" aria-expanded="false">
Toggle Section
</button>
</h2>
This is the most robust pattern. The heading remains in the document outline, and the button inside it is fully interactive with built-in keyboard support. Screen reader users can find it both when navigating by headings and when navigating by interactive elements. The aria-expanded attribute communicates whether the associated section is open or closed.
The <li> element has an implicit ARIA role of listitem, and the WHATWG HTML specification restricts which roles can be applied to it. The button role is not among the roles permitted on <li>. When you set role="button" on a <li>, you’re telling assistive technologies that the element is a button, but the browser and the spec still recognize it as a list item. This creates a semantic conflict that can confuse screen readers and other assistive tools, leading to a degraded experience for users who rely on them.
Beyond the validation error, there are practical accessibility concerns. A real <button> element comes with built-in keyboard support (it’s focusable and activatable with Enter or Space), whereas a <li> with role="button" lacks these behaviors by default. You would need to manually add tabindex, keyboard event handlers, and focus styling—effectively recreating what <button> gives you for free. This is error-prone and violates the ARIA principle of preferring native HTML elements over ARIA role overrides.
How to Fix
There are several approaches depending on your use case:
- Place a <button> inside each <li> — This is the best approach when you have a list of actions, as it preserves list semantics while providing proper button functionality.
- Use <button> elements directly — If the items aren’t truly a list, drop the <ul>/<li> structure and use <button> elements instead.
- Use a <div> or <span> with role="button" — If you cannot use a native <button> for some reason, these elements accept the button role. You’ll also need to add tabindex="0" and keyboard event handling yourself.
Examples
❌ Invalid: role="button" on <li> elements
<ul>
<li role="button">Copy</li>
<li role="button">Paste</li>
<li role="button">Delete</li>
</ul>
This triggers the validation error because <li> does not permit the button role.
✅ Fixed: Using <button> elements inside <li>
<ul>
<li><button type="button">Copy</button></li>
<li><button type="button">Paste</button></li>
<li><button type="button">Delete</button></li>
</ul>
This preserves the list structure while providing proper, accessible button behavior with no extra work.
✅ Fixed: Using standalone <button> elements
If the list structure isn’t meaningful, remove it entirely:
<div>
<button type="button">Copy</button>
<button type="button">Paste</button>
<button type="button">Delete</button>
</div>
✅ Fixed: Using a toolbar pattern
For a group of related actions, the ARIA toolbar pattern is a great fit:
<div role="toolbar" aria-label="Text actions">
<button type="button">Copy</button>
<button type="button">Paste</button>
<button type="button">Delete</button>
</div>
✅ Fixed: Using role="button" on a permitted element
If you truly cannot use a native <button>, a <div> or <span> can accept the button role. Note that you must manually handle focus and keyboard interaction:
<div role="button" tabindex="0">Copy</div>
However, this approach is almost always inferior to using a native <button> and should only be used as a last resort. Native elements provide keyboard behavior, form integration, and consistent styling hooks that are difficult to replicate reliably.
The http-equiv attribute on the <meta> element is designed to simulate certain HTTP response headers when a server isn’t configured to send them directly. However, the HTML specification only permits a limited set of values for this attribute. According to the WHATWG HTML living standard, the valid http-equiv values are:
- content-type — an alternative way to declare character encoding
- default-style — sets the preferred stylesheet
- refresh — redirects or reloads the page after a delay
- x-ua-compatible — specifies document compatibility mode for Internet Explorer
- content-security-policy — declares a content security policy
Using Cache-Control as an http-equiv value is a pattern that originated in early web development, when some browsers attempted to honor cache directives set through <meta> tags. In practice, modern browsers ignore <meta http-equiv="Cache-Control"> entirely. Caching behavior is determined by actual HTTP response headers sent by the server, not by <meta> tags in the document body. This means the tag not only triggers a validation error but also has no practical effect — it gives a false sense of control over caching while doing nothing.
This matters for several reasons. Invalid HTML can cause unexpected behavior in browsers, particularly edge cases with older or less common user agents. It also undermines confidence in your markup — if a validator flags issues, it becomes harder to spot genuinely important errors. Additionally, relying on a non-functional tag for caching can lead to real problems if developers assume caching is being handled when it isn’t.
The correct approach is to configure cache-control headers on your web server or application layer. Every major web server and framework provides a straightforward way to set Cache-Control HTTP headers.
For Apache, you can add this to your .htaccess or server configuration:
Header set Cache-Control "no-cache, no-store, must-revalidate"
For Nginx, use:
add_header Cache-Control "no-cache, no-store, must-revalidate";
In a Node.js/Express application:
res.set('Cache-Control', 'no-cache, no-store, must-revalidate');
Examples
Invalid: Using Cache-Control as an http-equiv value
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
<meta http-equiv="Cache-Control" content="no-cache">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
</head>
<body>
<p>This page attempts to control caching via meta tags.</p>
</body>
</html>
In this example, all three <meta> tags are problematic. Cache-Control and Pragma are not valid http-equiv values. While Expires was historically used, it is also not in the current list of conforming values in the WHATWG specification.
Fixed: Removing invalid <meta> tags
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
<!-- Cache-Control should be set via server HTTP headers -->
</head>
<body>
<p>Caching is now properly handled server-side.</p>
</body>
</html>
The invalid <meta> tags are removed entirely. Cache behavior is configured on the server, where it actually takes effect.
Valid: Using a permitted http-equiv value
<!DOCTYPE html>
<html lang="en">
<head>
<title>Redirecting</title>
<meta http-equiv="refresh" content="5;url=https://example.com">
</head>
<body>
<p>You will be redirected in 5 seconds.</p>
</body>
</html>
This example uses refresh, which is a valid http-equiv value. It demonstrates what the attribute is actually designed for — a small set of well-defined, browser-supported directives.
When you write a phone link using <a href="callto:...">, you may encounter two distinct problems at once. First, the callto: scheme is a legacy, non-standard protocol originally associated with Skype. The correct and widely supported URI scheme for telephone links is tel:, as defined by RFC 3966. Second, spaces within URI scheme data are illegal characters. URIs must not contain unencoded spaces anywhere, and telephone URIs specifically expect a compact phone number composed of digits, hyphens (-), dots (.), and an optional leading plus sign (+) for international dialing.
The W3C validator raises this error because the value provided to href violates URI syntax rules. Browsers may still attempt to handle the link, but behavior will be inconsistent — some mobile browsers may not recognize callto: at all, and spaces in the URI can cause the number to be parsed incorrectly or truncated. Using the standard tel: scheme with a properly formatted number ensures the link works reliably across devices and platforms, including mobile phones, VoIP applications, and assistive technologies.
How to fix it
- Replace callto: with tel: — The tel: scheme is the standard for phone number links and is supported by all modern browsers and mobile operating systems.
- Remove spaces and slashes — Strip out any spaces, slashes, or parentheses from the phone number. These characters are not valid in a tel: URI without percent-encoding, and they serve no functional purpose in the link target.
- Use a leading + for international numbers — If applicable, include the full international dialing code prefixed with + (e.g., +1 for the US, +49 for Germany). This makes the link work regardless of the caller’s location.
- Optional visual separators — If you want visual separators within the href for readability in your source code, use hyphens (-) or dots (.), which are permitted in tel: URIs. However, the simplest and safest approach is digits only (plus the optional leading +).
Examples
Incorrect: callto: with spaces and slashes
This triggers the validator error because spaces and slashes are illegal in URI scheme data, and callto: is non-standard.
<a href="callto:07142/ 12 34 5">Call us</a>
Incorrect: tel: with spaces
Even with the correct tel: scheme, spaces in the phone number are still invalid URI characters.
<a href="tel:07142 12 34 5">Call us</a>
Correct: tel: with digits only
<a href="tel:0714212345">Call us</a>
Correct: International number with + prefix
<a href="tel:+490714212345">Call us</a>
Correct: Using hyphens for readability
Hyphens are valid characters in tel: URIs and can improve source code readability without affecting functionality.
<a href="tel:+49-07142-12345">Call us</a>
Displaying a formatted number to the user
You can still show a human-friendly formatted number as the visible link text while keeping the href value clean and valid.
<a href="tel:+490714212345">+49 (0) 7142 / 12 34 5</a>
This approach gives you the best of both worlds: the link text is easy for users to read, and the href value is a valid, standards-compliant tel: URI that works reliably across all devices and passes W3C validation.
The http-equiv attribute on <meta> elements acts as a pragma directive, simulating the effect of an HTTP response header. The HTML specification defines a strict list of allowed values, including content-type, default-style, refresh, x-ua-compatible, and content-security-policy. Any value not on this list — such as cleartype — is considered invalid and will trigger a validation error.
The <meta http-equiv="cleartype" content="on"> tag was a proprietary Microsoft extension designed to activate ClearType text smoothing in Internet Explorer Mobile 6 and 7 on Windows Phone. ClearType is a sub-pixel rendering technology that improves the readability of text on LCD screens. Since these browsers are long obsolete, this meta tag serves no practical purpose today. No modern browser recognizes or acts on it.
Keeping invalid meta tags in your HTML has several downsides:
- Standards compliance: It produces W3C validation errors, which can mask other, more important issues in your markup.
- Code cleanliness: Dead code clutters your document head and confuses developers who may not know its history.
- No functional benefit: Since no current browser or rendering engine uses this directive, it provides zero value.
The fix is straightforward: remove the <meta http-equiv="cleartype"> tag. If your project requires smooth font rendering on modern browsers, CSS properties like font-smooth (non-standard) or -webkit-font-smoothing and -moz-osx-font-smoothing can be used instead, though these are also non-standard and should be used with care.
Examples
❌ Invalid: Using cleartype as an http-equiv value
<head>
<meta charset="utf-8">
<meta http-equiv="cleartype" content="on">
<title>My Page</title>
</head>
This triggers the validation error because cleartype is not a valid value for http-equiv.
✅ Fixed: Remove the invalid meta tag
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
Simply removing the tag resolves the error with no loss of functionality in modern browsers.
✅ Alternative: Use CSS for font smoothing if needed
If you want to influence text rendering, use CSS instead of a non-standard meta tag:
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
Note that these CSS properties are themselves non-standard and primarily affect macOS and iOS rendering. On Windows, modern browsers already apply ClearType or DirectWrite smoothing automatically without any developer intervention.
The combobox role represents a composite widget that combines a text input with a popup (typically a listbox) that helps the user set the value of the input. It’s a common pattern seen in autocomplete fields, search suggestions, and dropdown selects with type-ahead functionality.
The reason the validator rejects role="combobox" on <input> is rooted in how the HTML specification defines allowed ARIA roles for each element. An <input type="text"> already carries an implicit role of textbox, and the spec only permits a limited set of explicit roles on it (such as combobox in ARIA 1.2 contexts, but W3C HTML validation may still flag this depending on the validator’s conformance rules). When the validator encounters a role that isn’t in its allowed list for that element, it raises this error.
Why This Matters
- Standards compliance: Using roles outside the permitted set for an element violates the HTML specification, which can lead to unpredictable behavior across browsers and assistive technologies.
- Accessibility: Assistive technologies like screen readers rely on correct role assignments to convey the widget’s purpose. A misapplied role can confuse users who depend on these tools, making the combobox harder to navigate or understand.
- Browser interop: Browsers may handle conflicting implicit and explicit roles inconsistently, leading to different experiences across platforms.
How to Fix It
There are two main approaches depending on which ARIA pattern you follow:
Approach 1: ARIA 1.1 Pattern (Container as Combobox)
In the ARIA 1.1 combobox pattern, the role="combobox" is placed on a container element (like a <div>) that wraps the <input>. This is the approach most likely to pass W3C validation without issues.
Approach 2: ARIA 1.2 Pattern (Input as Combobox)
ARIA 1.2 moved the combobox role directly onto the <input> element itself, which is a simpler and more widely adopted pattern in practice. However, the W3C HTML Validator may still flag this as an error if its conformance rules haven’t been updated to reflect ARIA 1.2. If passing validation is a strict requirement, use Approach 1.
Examples
❌ Incorrect: role="combobox" directly on <input>
This triggers the validation error:
<input type="text" role="combobox" aria-autocomplete="list">
✅ Correct: ARIA 1.1 pattern with container element
The role="combobox" is placed on the wrapping <div>, and the <input> inside it handles text entry:
<div role="combobox" aria-haspopup="listbox" aria-owns="suggestions" aria-expanded="false">
<input type="text" aria-autocomplete="list" aria-controls="suggestions">
</div>
<ul id="suggestions" role="listbox" hidden>
<li role="option" id="opt-1">Apple</li>
<li role="option" id="opt-2">Banana</li>
<li role="option" id="opt-3">Cherry</li>
</ul>
Key attributes explained:
- role="combobox" on the <div> defines the overall widget for assistive technologies.
- aria-haspopup="listbox" tells screen readers that this widget has a popup of type listbox.
- aria-owns="suggestions" establishes an ownership relationship between the combobox and the listbox, even if they aren’t parent-child in the DOM.
- aria-expanded="false" indicates whether the popup is currently visible. Update this to "true" via JavaScript when the list is shown.
- aria-autocomplete="list" on the <input> signals that suggestions will be presented in a list as the user types.
- aria-controls="suggestions" on the <input> links it to the listbox it controls.
✅ Correct: ARIA 1.2 pattern using <input> with role="combobox" (if validation is not strict)
If your project follows ARIA 1.2 and you can tolerate or suppress the validation warning, this pattern is widely supported by modern browsers and screen readers:
<label for="fruit">Choose a fruit</label>
<input
id="fruit"
type="text"
role="combobox"
aria-autocomplete="list"
aria-expanded="false"
aria-controls="fruit-list"
aria-haspopup="listbox">
<ul id="fruit-list" role="listbox" hidden>
<li role="option" id="fruit-1">Apple</li>
<li role="option" id="fruit-2">Banana</li>
<li role="option" id="fruit-3">Cherry</li>
</ul>
This is the pattern recommended by the WAI-ARIA Authoring Practices Guide and is the most commonly implemented in modern component libraries. If validation compliance is required, wrap the input in a container and move the role="combobox" there as shown in Approach 1.
Whichever approach you choose, remember that ARIA attributes alone don’t create behavior — you’ll need JavaScript to toggle aria-expanded, manage focus, handle keyboard navigation, and update aria-activedescendant as the user moves through options.
The autocomplete attribute helps browsers automatically fill in form fields with previously saved user data. The HTML specification defines a strict set of valid autofill field names, and "company" is not among them. While “company” might seem like an intuitive choice, the spec uses "organization" to represent a company name, business name, or other organizational name associated with the person or address in the form.
Using an invalid autocomplete value means browsers won’t recognize the field’s purpose and cannot offer relevant autofill suggestions. This degrades the user experience — especially on mobile devices where autofill significantly speeds up form completion. It also impacts accessibility, as assistive technologies may rely on valid autocomplete tokens to help users understand and complete forms efficiently.
The full list of valid autofill field names is defined in the WHATWG HTML Living Standard. Some commonly used values include "name", "email", "tel", "street-address", "postal-code", "country", and "organization". When choosing a value, always refer to the specification rather than guessing a name that seems logical.
Examples
❌ Invalid: using "company" as an autocomplete value
<label for="company">Company Name</label>
<input type="text" id="company" name="company" autocomplete="company">
This triggers the validation error because "company" is not a recognized autofill field name.
✅ Valid: using "organization" instead
<label for="company">Company Name</label>
<input type="text" id="company" name="company" autocomplete="organization">
The value "organization" is the spec-defined autofill field name for “the company, organization, institution, or other entity associated with the person, address, or contact information in the other fields associated with this field.”
✅ Valid: using "organization" with a section and purpose
You can combine "organization" with other valid tokens for more specificity:
<label for="work-org">Employer</label>
<input type="text" id="work-org" name="employer" autocomplete="section-work organization">
This tells the browser that the field is for an organization name within a specific named section of the form, which is useful when a form collects information about multiple entities.
Common Autofill Field Names for Business Forms
Here are some valid autocomplete values you might use alongside "organization" in a business-related form:
- "organization" — company or organization name
- "organization-title" — job title (e.g., “Software Engineer”, “CEO”)
- "name" — full name of the contact person
- "email" — email address
- "tel" — telephone number
- "street-address" — full street address
Using the correct values ensures browsers can provide meaningful autofill suggestions, making your forms faster and easier to complete.
The WAI-ARIA specification defines a strict set of valid role values, and "complimentary" is not among them. This is a straightforward typo — "complimentary" (meaning “expressing praise or given free of charge”) versus "complementary" (meaning “serving to complete or enhance something”). When a browser or assistive technology encounters an unrecognized role value, it ignores it. This means screen reader users lose the semantic meaning that the <aside> element would normally convey, making it harder for them to understand the page structure and navigate effectively.
The <aside> element already carries an implicit ARIA role of complementary as defined by the HTML specification. This means assistive technologies automatically treat <aside> as complementary content without any explicit role attribute. Adding role="complementary" to an <aside> is redundant. The simplest and best fix is to remove the misspelled role attribute and let the element’s native semantics do the work.
If you have a specific reason to explicitly set the role — for example, when overriding it with a different valid role — make sure the value is spelled correctly and is an appropriate role for the element.
Examples
❌ Incorrect: misspelled role value
<aside role="complimentary">
<h2>Related Articles</h2>
<ul>
<li><a href="/guide-one">Getting started guide</a></li>
<li><a href="/guide-two">Advanced techniques</a></li>
</ul>
</aside>
The value "complimentary" is not a valid ARIA role. Assistive technologies will ignore it, and the element loses its semantic meaning.
✅ Correct: remove the redundant role
<aside>
<h2>Related Articles</h2>
<ul>
<li><a href="/guide-one">Getting started guide</a></li>
<li><a href="/guide-two">Advanced techniques</a></li>
</ul>
</aside>
The <aside> element already implies role="complementary", so no explicit role is needed. This is the recommended approach.
✅ Correct: explicitly set the properly spelled role
<aside role="complementary">
<h2>Related Articles</h2>
<ul>
<li><a href="/guide-one">Getting started guide</a></li>
<li><a href="/guide-two">Advanced techniques</a></li>
</ul>
</aside>
This is valid but redundant. It may be appropriate in rare cases where you want to be explicit for clarity or to work around edge cases with certain assistive technologies.
Quick reference for similar typos
| Incorrect (typo) | Correct | Implicit on element |
|---|---|---|
| complimentary | complementary | <aside> |
| navagation | navigation | <nav> |
| presentaion | presentation | (none) |
Always double-check role values against the WAI-ARIA role definitions to ensure they are valid. When an HTML element already provides the semantics you need, prefer using the element without an explicit role — this follows the first rule of ARIA: “If you can use a native HTML element with the semantics and behavior you require, do so.”
The autocomplete attribute helps browsers autofill form fields with previously saved user data. The HTML specification defines a strict set of valid values, and each one maps to a specific type of information (like a name, email address, phone number, or street address). The string "contact" by itself is not a valid autofill field name — it’s a contact type token, which is a modifier meant to be combined with a field name to distinguish between different types of contact information.
The HTML spec defines two contact type tokens: "home", "work", "mobile", "fax", and "pager" (for phone-related fields), as well as the broader "shipping" and "billing" scoping tokens. The token "contact" doesn’t exist as a standalone value at all. You may have confused it with a contact type prefix pattern like "home email" or "work tel", or you may have intended to use a specific field name entirely.
Getting the autocomplete value right matters for several reasons. Browsers rely on these exact tokens to offer relevant autofill suggestions. Screen readers and assistive technologies may also use this information to help users understand what data a field expects. An invalid value means the browser will likely ignore the attribute entirely, degrading the user experience — especially on mobile devices where autofill is heavily used.
To fix the issue, determine what kind of information the input field is collecting and use the appropriate autofill field name. Common valid values include "name", "email", "tel", "street-address", "postal-code", "organization", and "username". If you want to indicate that this is specifically a contact email or phone (as opposed to, say, a billing one), you don’t use "contact" — instead, you can omit the modifier entirely or use a section-scoping approach.
Examples
❌ Invalid: Using “contact” as the autocomplete value
<label for="email">Contact Email</label>
<input type="email" id="email" name="email" autocomplete="contact">
The value "contact" is not a recognized autofill field name, so the browser cannot determine what to autofill.
✅ Fixed: Using a valid autofill field name
<label for="email">Contact Email</label>
<input type="email" id="email" name="email" autocomplete="email">
The value "email" is a valid autofill field name that tells the browser to suggest saved email addresses.
✅ Fixed: Using a valid combination with a section or contact type token
If you need to differentiate between types of phone numbers, you can use tokens like "home", "work", or "mobile" as prefixes:
<label for="work-tel">Work Phone</label>
<input type="tel" id="work-tel" name="work-tel" autocomplete="work tel">
<label for="home-email">Personal Email</label>
<input type="email" id="home-email" name="home-email" autocomplete="home email">
Common valid autocomplete values
Here are some frequently used valid autofill field names:
| Value | Purpose |
|---|---|
| "name" | Full name |
| "email" | Email address |
| "tel" | Phone number |
| "username" | Username |
| "new-password" | New password (for registration) |
| "current-password" | Existing password (for login) |
| "street-address" | Street address |
| "postal-code" | ZIP or postal code |
| "country-name" | Country name |
| "organization" | Company or organization |
| "off" | Disable autofill |
For the complete list of valid values and their permitted combinations, refer to the WHATWG autofill specification.
The http-equiv attribute on the <meta> element simulates HTTP response headers. However, the HTML living standard only allows a specific set of values for http-equiv, and Content-Script-Type is not among them. The allowed values include content-type, default-style, refresh, x-ua-compatible, and content-security-policy.
In HTML 4.01, <meta http-equiv="Content-Script-Type" content="text/javascript"> was used to tell the browser which scripting language to assume for inline event handlers (like onclick). Since JavaScript is now the only scripting language supported by browsers, this declaration serves no purpose. Every modern browser already assumes JavaScript by default, making this meta tag completely redundant.
Removing this tag has no effect on your page’s behavior. Your scripts will continue to work exactly as before. If you’re maintaining a legacy codebase, you can safely delete this line during any cleanup or modernization effort.
Examples
❌ Invalid: using Content-Script-Type
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Script-Type" content="text/javascript">
<title>My Page</title>
</head>
This triggers the validation error because Content-Script-Type is not a valid http-equiv value in modern HTML.
✅ Fixed: remove the obsolete meta tag
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
Simply remove the <meta http-equiv="Content-Script-Type"> line. No replacement is needed — browsers already default to JavaScript for all script handling.
✅ Valid http-equiv values for reference
Here are some examples of http-equiv values that are valid in modern HTML:
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta http-equiv="refresh" content="30">
<meta http-equiv="content-security-policy" content="default-src 'self'">
<title>My Page</title>
</head>
The http-equiv attribute accepts a specific set of predefined values, and the validator checks both the value itself and its formatting. When the validator reports a bad value of ""Content-Security-Policy"" (note the doubled quotes), it means the actual attribute value being parsed includes literal quotation mark characters as part of the string. The browser sees the first " as opening the attribute, then immediately sees the second " as closing it — resulting in a malformed tag that won’t work as intended.
This matters for several reasons. Content-Security-Policy delivered via a <meta> tag is a critical security mechanism that restricts which resources your page can load. If the tag is malformed, the browser will silently ignore the policy, leaving your site without the CSP protections you intended. There’s no visual indication that the policy failed to apply, making this a particularly dangerous bug.
Common causes of this issue include:
- Copying code from a word processor or CMS that converts straight quotes (") into curly/smart quotes (" and ").
- Double-escaping in templates where a templating engine adds quotes around a value that already has quotes in the markup.
- Manual typos where quotes are accidentally duplicated.
To fix this, open your HTML source in a plain-text editor (not a word processor) and ensure the http-equiv value is wrapped in exactly one pair of standard straight double quotes with no extra quote characters inside.
Examples
Incorrect — doubled quotes around the value
<meta http-equiv=""Content-Security-Policy"" content="default-src 'self';">
The validator interprets this as an http-equiv attribute with an empty value (""), followed by unrecognized content (Content-Security-Policy""), producing the error.
Incorrect — curly/smart quotes
<meta http-equiv="Content-Security-Policy" content="default-src 'self';">
Smart quotes (" and ") are not valid attribute delimiters in HTML. They become part of the attribute value itself, causing the validator to reject it.
Incorrect — HTML entity quotes inside the attribute
<meta http-equiv=""Content-Security-Policy"" content="default-src 'self';">
Using " inside the attribute value embeds literal quote characters into the value string, which makes it invalid.
Correct — single pair of straight double quotes
<meta http-equiv="Content-Security-Policy" content="default-src 'self';">
Correct — full document example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https:; script-src 'self';">
<title>CSP Example</title>
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>
The http-equiv value Content-Security-Policy must be spelled exactly as shown — it is case-insensitive per the HTML spec, but using the canonical casing is recommended for clarity. The actual policy directives go in the content attribute, not in http-equiv. If you’re using a templating engine or CMS, check the generated HTML source (via “View Page Source” in your browser) to confirm the output contains clean, straight quotes with no doubling.
Why This Is an Issue
In HTML 4 and XHTML, the Content-Style-Type HTTP header (and its <meta http-equiv> equivalent) told the browser which stylesheet language to use when interpreting inline style attributes and <style> elements. This was theoretically necessary because the specification allowed for alternative stylesheet languages beyond CSS.
In practice, CSS became the only stylesheet language browsers support. The HTML living standard (maintained by WHATWG) recognizes this reality and defines a strict list of valid http-equiv values. Content-Style-Type is not among them. The only valid values include content-type, default-style, refresh, x-ua-compatible, content-security-policy, and a few others defined in the spec.
Because every browser defaults to CSS for all styling, this meta tag serves no functional purpose. Keeping it in your markup only produces a validation error and adds unnecessary bytes to your document.
Similarly, the related Content-Script-Type meta tag (which declared the default scripting language) is also obsolete for the same reasons — JavaScript is the universal default.
How to Fix It
The fix is straightforward: remove the <meta http-equiv="Content-Style-Type" ...> tag from your document’s <head>. No replacement is needed. Browsers will interpret all stylesheets as CSS and all scripts as JavaScript without any explicit declaration.
If you inherited this tag from a legacy template or an older CMS, you can safely delete it with no impact on your site’s appearance or behavior.
Examples
❌ Invalid: Using the obsolete Content-Style-Type pragma
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Style-Type" content="text/css">
<title>My Page</title>
</head>
This triggers the validator error: Bad value “Content-Style-Type” for attribute “http-equiv” on element “meta”.
✅ Valid: Simply remove the obsolete meta tag
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
No replacement is needed. CSS is already the default stylesheet language in all browsers.
❌ Invalid: Both Content-Style-Type and Content-Script-Type (common in legacy templates)
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Style-Type" content="text/css">
<meta http-equiv="Content-Script-Type" content="text/javascript">
<title>Legacy Page</title>
</head>
✅ Valid: Remove both obsolete declarations
<head>
<meta charset="utf-8">
<title>Legacy Page</title>
</head>
Both Content-Style-Type and Content-Script-Type are obsolete. Removing them has zero effect on how browsers render your page, and your HTML will pass validation cleanly.
The lang attribute on any HTML element must be a valid IANA language subtag (like en, fr, or ja), not a programming language name.
The lang attribute specifies the natural (human) language of the element’s content, used by browsers, screen readers, and search engines for accessibility and localization purposes. Values like csharp, javascript, or python are not valid because they are not human languages registered in the IANA Language Subtag Registry.
This issue commonly arises when using <code lang="csharp"> to indicate the programming language of a code snippet. While the intention makes sense, lang is not the right attribute for this purpose.
To indicate a programming language on a <code> element, the recommended convention from the HTML specification is to use the class attribute with a language- prefix (e.g., class="language-csharp"). This is also the pattern used by popular syntax highlighting libraries like Prism.js and highlight.js.
If the code content is written in a specific human language (like English), you can still use lang="en" alongside the class.
Bad Example
<pre>
<code lang="csharp">
Console.WriteLine("Hello, World!");
</code>
</pre>
Good Example
<pre>
<code class="language-csharp">
Console.WriteLine("Hello, World!");
</code>
</pre>
The datetime input type was originally proposed to allow users to enter a date and time along with a timezone. However, no browser ever fully implemented it with a usable UI, and the WHATWG HTML Living Standard officially dropped it. Because datetime is not a recognized value for the type attribute, browsers treat <input type="datetime"> as <input type="text"> — a plain text field with no built-in date or time validation, no native picker, and no semantic meaning for assistive technologies.
This matters for several reasons. From an accessibility standpoint, screen readers and other assistive technologies rely on the correct type attribute to communicate the purpose of an input to users. A fallback to type="text" provides no such context. From a validation and user experience perspective, native date/time input types offer built-in pickers on mobile and desktop browsers, enforce format constraints, and support attributes like min, max, and step — none of which work correctly when the type falls back to plain text. From a standards compliance perspective, using an obsolete type means your HTML is invalid, which can cause issues with automated testing, SEO audits, and quality assurance processes.
How to Fix It
Choose a replacement based on what data you need to collect:
- type="datetime-local" — Collects both a date and a time without timezone information. This is the most direct replacement for the old datetime type.
- type="date" — Collects only a date (year, month, day).
- type="time" — Collects only a time (hours, minutes, optionally seconds).
- type="text" with a JavaScript widget — Use this approach if you need timezone-aware datetime input, since no native HTML input type currently handles timezones.
The datetime-local input supports useful attributes like min, max, step, value, and name, giving you control over the range and granularity of selectable values. The step attribute is specified in seconds (e.g., step="60" for one-minute intervals, step="1" to allow seconds).
Examples
❌ Invalid: Using the obsolete datetime type
This triggers the W3C validation error:
<form>
<label>
Meeting:
<input type="datetime" name="meeting">
</label>
</form>
✅ Fixed: Using datetime-local
Replace datetime with datetime-local to collect both a date and time:
<form>
<label>
Meeting:
<input type="datetime-local" name="meeting" step="60">
</label>
</form>
✅ Fixed: Using separate date and time inputs
If you prefer to collect the date and time independently, split them into two fields:
<form>
<label>
Meeting date:
<input type="date" name="meeting-date" min="2025-01-01" max="2025-12-31">
</label>
<label>
Meeting time:
<input type="time" name="meeting-time" step="900">
</label>
</form>
In this example, step="900" on the time input sets 15-minute intervals (900 seconds).
✅ Fixed: Using datetime-local with constraints
You can set minimum and maximum allowed values using the standard datetime-local format (YYYY-MM-DDThh:mm):
<form>
<label>
Appointment:
<input
type="datetime-local"
name="appointment"
min="2025-06-01T09:00"
max="2025-06-30T17:00"
step="1800"
required>
</label>
<button type="submit">Book</button>
</form>
This restricts selection to June 2025 during business hours, in 30-minute increments, and makes the field required.
The HTML specification defines a set of implicit ARIA roles (also called “native semantics”) for many HTML elements. The <dialog> element’s implicit role is dialog, which means assistive technologies like screen readers already announce it correctly without any explicit ARIA markup. When you add role="dialog" to a <dialog> element, you’re restating what the browser and accessibility tree already know—and the ARIA in HTML specification explicitly restricts this.
The ARIA in HTML spec maintains a list of allowed roles for each HTML element. For <dialog>, the only permitted role override is alertdialog (for dialogs that require an immediate response from the user). Setting role="dialog" is not listed as an allowed value because it duplicates the native semantics, and the spec treats such redundancy as a conformance error. This is why the W3C Validator reports: Bad value “dialog” for attribute “role” on element “dialog”.
Why this matters
- Standards compliance: The W3C Validator enforces the ARIA in HTML specification, which prohibits redundant role assignments on elements that already carry that role implicitly. Valid markup ensures your pages conform to web standards.
- Accessibility clarity: While most assistive technologies handle redundant roles gracefully today, unnecessary ARIA attributes add noise to the codebase and can cause confusion about whether the element’s native semantics are intentionally being overridden. The first rule of ARIA is: don’t use ARIA if a native HTML element already provides the semantics you need.
- Maintainability: Removing redundant attributes keeps your HTML clean and easier to maintain. Future developers won’t need to wonder whether the explicit role was added intentionally to work around a bug.
How to fix it
- Locate any <dialog> element with a role="dialog" attribute.
- Remove the role attribute entirely.
- If you need the dialog to behave as an alert dialog (one that interrupts the user and demands immediate attention), use role="alertdialog" instead—this is the one permitted role override for <dialog>.
Examples
Incorrect — redundant role causes a validation error
<dialog role="dialog">
<h2>Confirm action</h2>
<p>Are you sure you want to proceed?</p>
<button>Cancel</button>
<button>Confirm</button>
</dialog>
Correct — relying on the implicit role
<dialog>
<h2>Confirm action</h2>
<p>Are you sure you want to proceed?</p>
<button>Cancel</button>
<button>Confirm</button>
</dialog>
The <dialog> element automatically exposes role="dialog" in the accessibility tree, so no explicit attribute is needed.
Correct — using an allowed role override
If the dialog represents an urgent alert that requires immediate user interaction, you can override the role with alertdialog:
<dialog role="alertdialog" aria-labelledby="alert-title" aria-describedby="alert-desc">
<h2 id="alert-title">Session expiring</h2>
<p id="alert-desc">Your session will expire in 60 seconds. Do you want to continue?</p>
<button>Stay signed in</button>
</dialog>
This is valid because alertdialog is explicitly listed as a permitted role for the <dialog> element in the ARIA in HTML specification. Note that aria-labelledby and aria-describedby are strongly recommended for alert dialogs so assistive technologies can announce the title and description properly.
The HTML <input> element’s type attribute only accepts a specific set of predefined values defined in the HTML specification. These include values like text, password, email, number, date, datetime-local, checkbox, radio, and others. The value dob — presumably short for “date of birth” — is not among them.
When a browser encounters an invalid type value, it doesn’t throw an error or prevent the page from loading. Instead, it treats the input as type="text". This means the input might appear to work, but you lose important benefits: there’s no native date picker UI, no built-in date format validation, and no appropriate mobile keyboard. The W3C validator flags this to help you catch the mistake early.
This matters for several reasons:
- Accessibility: Valid input types provide semantic meaning to assistive technologies. A type="date" input tells screen readers that a date is expected, enabling better guidance for users.
- User experience: Native date inputs offer platform-appropriate date pickers on mobile and desktop, reducing input errors.
- Standards compliance: Using invalid attribute values produces unpredictable behavior across browsers and can break future compatibility.
To fix this issue, replace type="dob" with a recognized type. For a date of birth field, type="date" is the most appropriate choice. If you need more control over formatting, you can use type="text" with a JavaScript date picker library or custom validation.
Examples
❌ Invalid: using type="dob"
<label for="dob">Date of Birth:</label>
<input type="dob" id="dob" name="dob">
The browser will treat this as a plain text input, and the W3C validator will report: Bad value “dob” for attribute “type” on element “input”.
✅ Fixed: using type="date"
<label for="dob">Date of Birth:</label>
<input type="date" id="dob" name="dob">
This uses the native HTML date input, which provides a built-in date picker in most modern browsers. You can also constrain the date range with min and max attributes:
<label for="dob">Date of Birth:</label>
<input type="date" id="dob" name="dob" min="1900-01-01" max="2025-12-31">
✅ Fixed: using type="text" with a JavaScript date picker
If you need more control over the date picker’s appearance or need to support older browsers that lack native date input support, use type="text" and enhance it with JavaScript:
<label for="dob">Date of Birth:</label>
<input type="text" id="dob" name="dob" placeholder="YYYY-MM-DD">
You can then attach a JavaScript date picker library (such as Flatpickr, Pikaday, or a framework-specific component) to this input for a custom date selection experience. When using this approach, make sure to add appropriate aria-* attributes and validation to maintain accessibility.
Valid type values for reference
Here are some commonly used valid type values for the <input> element:
- text — plain text input
- date — date picker (year, month, day)
- datetime-local — date and time picker (no timezone)
- month — month and year picker
- number — numeric input
- email — email address input
- tel — telephone number input
- password — masked text input
Always choose the type that best matches the data you’re collecting. For a date of birth, type="date" is the most semantically correct and user-friendly option.
The hreflang attribute on a link element tells browsers and search engines the language and optional region of the linked resource. Its value must be a valid BCP 47 language tag, which follows a specific structure: a primary language subtag (like en, fr, de) optionally followed by a region subtag (like US, GB, FR). The region subtag must be a valid two-letter ISO 3166-1 alpha-2 country code.
The tag en-EN is invalid because EN is not a recognized country code. There is no country with the code “EN” — it’s a common mistake where the language code is simply repeated as the region. This pattern shows up frequently with other languages too, such as fr-FR (which happens to be valid because FR is the country code for France) leading people to assume en-EN follows the same logic. However, EN is not assigned to any country in ISO 3166-1, so the validator correctly rejects it.
Why This Matters
- SEO and internationalization: Search engines like Google use hreflang values to serve the correct localized version of a page. An invalid value may cause search engines to ignore the tag entirely, undermining your localization strategy.
- Standards compliance: Browsers and tools rely on well-formed language tags to apply correct locale-specific behavior such as font selection, date formatting hints, and spell-checking language.
- Accessibility: Screen readers and assistive technologies may use language information to select the correct speech synthesis voice. Invalid language tags can lead to content being read in the wrong accent or language.
Common Invalid Region Subtags
This mistake isn’t limited to English. Here are some commonly seen invalid patterns and their corrections:
| Invalid | Why It’s Wrong | Valid Alternatives |
|---|---|---|
| en-EN | EN is not a country code | en, en-US, en-GB, en-AU |
| de-DE | ✅ Actually valid — DE is Germany | de, de-DE, de-AT, de-CH |
| ja-JA | JA is not a country code | ja, ja-JP |
| zh-ZH | ZH is not a country code | zh, zh-CN, zh-TW |
| ko-KO | KO is not a country code | ko, ko-KR |
The key takeaway: don’t assume you can double the language code to make a region subtag. Always verify against the ISO 3166-1 alpha-2 country code list.
How to Fix It
- If you don’t need a regional variant, just use the bare language subtag: en.
- If you need a specific regional variant, pair the language subtag with the correct country code: en-US for American English, en-GB for British English, en-AU for Australian English, etc.
- Look up the country code if you’re unsure. The region subtag corresponds to a country, not a language.
Examples
Incorrect — Invalid region subtag
<link rel="alternate" href="https://example.com/en/" hreflang="en-EN">
The region subtag EN does not correspond to any country and will trigger a validation error.
Correct — Language only
If the content is simply in English without a specific regional variant, omit the region:
<link rel="alternate" href="https://example.com/en/" hreflang="en">
Correct — Language with valid region subtags
When you need to differentiate between regional variants, use proper country codes:
<link rel="alternate" href="https://example.com/en-us/" hreflang="en-US">
<link rel="alternate" href="https://example.com/en-gb/" hreflang="en-GB">
<link rel="alternate" href="https://example.com/en-au/" hreflang="en-AU">
Correct — Full set of hreflang links with x-default
A typical multilingual setup with properly formed language tags:
<link rel="alternate" href="https://example.com/" hreflang="x-default">
<link rel="alternate" href="https://example.com/en/" hreflang="en">
<link rel="alternate" href="https://example.com/en-us/" hreflang="en-US">
<link rel="alternate" href="https://example.com/fr/" hreflang="fr">
<link rel="alternate" href="https://example.com/de/" hreflang="de">
<link rel="alternate" href="https://example.com/ja/" hreflang="ja">
Note the use of x-default to indicate the default or language-selection page — this is a special value recognized by search engines for fallback purposes.
The http-equiv attribute on the <meta> element is designed to simulate certain HTTP response headers directly in HTML. However, the HTML specification only permits a specific set of values: content-type, default-style, refresh, x-ua-compatible, and content-security-policy. Using Expires as a value for http-equiv will trigger a validation error because it falls outside this permitted set.
Historically, some older browsers and HTML versions were more lenient about which values could appear in http-equiv, and developers commonly used <meta http-equiv="Expires" content="0"> or similar patterns to try to prevent page caching. However, this approach was never reliable — browsers and caching proxies handle actual HTTP headers far more consistently than <meta> tag equivalents. Modern HTML formally disallows this value.
Beyond standards compliance, there are practical reasons to avoid this pattern. Many browsers simply ignore unrecognized http-equiv values, meaning the tag does nothing useful. Cache behavior is best controlled at the HTTP level, where servers, CDNs, and proxies all read and respect the headers. Relying on a <meta> tag for caching gives a false sense of control while cluttering your markup with invalid code.
To fix this issue, remove the <meta http-equiv="Expires" ...> tag from your HTML and configure the Expires or Cache-Control HTTP header on your web server.
Examples
Incorrect: Using Expires in http-equiv
This triggers the validation error:
<head>
<meta charset="UTF-8">
<meta http-equiv="Expires" content="0">
<meta http-equiv="Expires" content="Tue, 01 Jan 2025 00:00:00 GMT">
<title>My Page</title>
</head>
Correct: Remove the invalid <meta> tag
Simply remove the offending tag. Only use valid http-equiv values:
<head>
<meta charset="UTF-8">
<title>My Page</title>
</head>
Correct: Valid uses of http-equiv
For reference, here are examples of valid http-equiv values:
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="30">
<meta http-equiv="content-security-policy" content="default-src 'self'">
<meta http-equiv="default-style" content="main-stylesheet">
<title>My Page</title>
</head>
Correct: Set cache expiration via server configuration
The proper way to control caching is through HTTP response headers configured on your server.
Apache (.htaccess or server config):
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/html "access plus 1 day"
</IfModule>
Or using Cache-Control with mod_headers:
<IfModule mod_headers.c>
Header set Cache-Control "no-cache, no-store, must-revalidate"
Header set Expires "0"
</IfModule>
Nginx:
location ~* \.html$ {
expires 1d;
add_header Cache-Control "public, no-transform";
}
To prevent caching entirely in Nginx:
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
If you don’t have access to server configuration, many server-side languages let you set headers programmatically. For example, in PHP:
<?php
header("Expires: Tue, 01 Jan 2030 00:00:00 GMT");
header("Cache-Control: public, max-age=86400");
?>
By handling cache expiration at the server level, you get reliable behavior across all browsers, proxies, and CDNs — while keeping your HTML clean and standards-compliant.
The autocomplete attribute on a <form> element only accepts "on" or "off" as valid values — "false" is not a recognized keyword.
The autocomplete attribute controls whether the browser can automatically fill in form fields based on previously entered values. When set on a <form> element, it applies as the default behavior for all fields within that form. The valid values are "on" (the browser may auto-complete entries) and "off" (the browser should not auto-complete entries).
It’s a common mistake to use "true" or "false" since many other attributes and programming languages use boolean-style values. However, the HTML specification explicitly defines only "on" and "off" for the <form> element’s autocomplete attribute.
Note that individual <input> elements support a much wider range of autocomplete values (like "name", "email", "street-address", etc.), but these extended values do not apply to the <form> element itself.
Invalid Example
<form autocomplete="false" action="/submit" method="post">
<label for="email">Email</label>
<input type="email" id="email" name="email">
<button type="submit">Submit</button>
</form>
Valid Example
<form autocomplete="off" action="/submit" method="post">
<label for="email">Email</label>
<input type="email" id="email" name="email">
<button type="submit">Submit</button>
</form>
The hidden attribute indicates that an element is not yet, or is no longer, relevant to the current state of the page. Browsers will not render elements that have this attribute. It’s available on all HTML elements as a global attribute.
In HTML, boolean attributes like hidden, disabled, readonly, and checked follow special rules. Unlike attributes in programming languages where you might set a value to true or false, boolean attributes in HTML work by presence or absence:
- Present = the feature is on (e.g., hidden, hidden="", or hidden="hidden")
- Absent = the feature is off (the attribute is simply not in the markup)
This is a common source of confusion. Writing hidden="false" does not make the element visible. Because the attribute is still present in the markup, the browser interprets it as “this element is hidden.” The actual string value "false" is ignored for the purpose of determining the boolean state. This can lead to frustrating bugs where elements remain invisible despite what looks like correct code.
According to the HTML specification, the only valid values for a boolean attribute are the empty string ("") or the attribute’s own name (e.g., hidden="hidden"). Any other value, including "true" or "false", is invalid and will trigger a W3C validator error.
How the hidden attribute works with newer values
Starting with more recent updates to the HTML specification, the hidden attribute also accepts the value "until-found". When set to hidden="until-found", the element remains hidden but can be revealed by the browser’s find-in-page feature or by fragment navigation. This is the only keyword value (besides the empty string and the attribute’s canonical name) that changes the attribute’s behavior. It does not change the fact that "false" is an invalid value.
How to fix it
- To hide an element, add the hidden attribute with no value.
- To show an element, remove the hidden attribute entirely from the markup.
- If you’re toggling visibility with JavaScript, use element.hidden = false (the JavaScript property, not the HTML attribute) or element.removeAttribute('hidden').
Examples
❌ Invalid: setting hidden to "false"
<!-- The element is STILL hidden and the markup is invalid -->
<div hidden="false">You won't see this text.</div>
❌ Invalid: setting hidden to "true"
<!-- "true" is also not a valid value for a boolean attribute -->
<p hidden="true">This paragraph is hidden, but the markup is invalid.</p>
✅ Valid: using hidden without a value
<div hidden>This element is hidden from the page.</div>
✅ Valid: using hidden with an empty string or its own name
<!-- Both of these are valid ways to write boolean attributes -->
<div hidden="">Hidden element</div>
<div hidden="hidden">Also a hidden element</div>
✅ Valid: showing the element by omitting hidden
<div>This element is visible because it has no hidden attribute.</div>
✅ Valid: using hidden="until-found"
<div hidden="until-found">
This content is hidden but can be found via browser search.
</div>
Toggling visibility with JavaScript
When dynamically showing or hiding elements, use the hidden property on the DOM element rather than setting the attribute to "false":
<button type="button" id="toggle">Toggle message</button>
<p id="message" hidden>Hello! Now you can see me.</p>
<script>
document.getElementById("toggle").addEventListener("click", function () {
const msg = document.getElementById("message");
msg.hidden = !msg.hidden; // Correctly toggles the boolean property
});
</script>
Using msg.hidden = false in JavaScript correctly removes the hidden attribute from the element. This is different from writing hidden="false" directly in HTML, which keeps the attribute present and triggers the validation error.
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