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 type attribute on an <a> element is an advisory hint that tells the browser what media type (MIME type) to expect at the linked resource. A valid MIME type follows a strict format: a type, a / separator, and a subtype (e.g., text/html, application/pdf, image/png). Each part must consist of token characters — letters, digits, and certain symbols — but not spaces.
This validation error occurs when the MIME type value contains a space or other unexpected character in a position where only token characters or a / are allowed. Common causes include:
- Accidental spaces within the MIME type (e.g., application/ pdf or application /pdf).
- Multiple MIME types separated by spaces (e.g., text/html text/plain), which is not valid since the attribute accepts only a single MIME type.
- Typos or copy-paste errors that introduce whitespace or non-token characters.
While the type attribute is purely advisory and browsers won’t refuse to follow a link based on it, an invalid value defeats its purpose and signals sloppy markup. Standards-compliant HTML ensures your pages are interpreted consistently and avoids confusing tools, screen readers, or other user agents that may parse this attribute.
Examples
Incorrect: Space within the MIME type
<a href="report.pdf" type="application/ pdf">Download Report</a>
The space after the / makes this an invalid MIME type.
Incorrect: Multiple MIME types separated by a space
<a href="data.csv" type="text/csv text/plain">Download Data</a>
The type attribute only accepts a single MIME type. The space between text/csv and text/plain triggers the error.
Incorrect: Leading or trailing spaces
<a href="photo.jpg" type=" image/jpeg ">View Photo</a>
Spaces before or after the MIME type are not permitted.
Correct: Valid MIME type with no spaces
<a href="report.pdf" type="application/pdf">Download Report</a>
Correct: Other common valid MIME types
<a href="data.csv" type="text/csv">Download Data</a>
<a href="photo.jpg" type="image/jpeg">View Photo</a>
<a href="archive.zip" type="application/zip">Download Archive</a>
Correct: MIME type with a parameter
MIME types can include parameters separated by a semicolon — no spaces are required, though a single space after the semicolon is permitted per the MIME specification:
<a href="page.html" type="text/html; charset=utf-8">View Page</a>
How to Fix
- Inspect the type value — look for any spaces within the type or subtype portions (before or after the /).
- Remove extra spaces — ensure the value is a single, properly formatted MIME type like type/subtype.
- Use only one MIME type — if you’ve listed multiple types, pick the one that accurately describes the linked resource.
- Verify the MIME type is valid — consult the IANA Media Types registry to confirm you’re using a recognized type.
- Consider removing the attribute — since type is purely advisory on <a> elements, if you’re unsure of the correct MIME type, omitting the attribute entirely is perfectly valid.
A MIME type (also called a media type) always follows the format type/subtype, such as text/html, application/pdf, or image/jpeg. The “type” part indicates the general category (e.g., text, image, application, audio, video), and the “subtype” specifies the exact format within that category. When the validator reports “Subtype missing,” it means the value you provided either lacks the /subtype portion or isn’t a valid MIME type structure at all.
A common cause of this error is misunderstanding the purpose of the type attribute on <a> elements. The type attribute is not used to change the behavior or appearance of the link (the way type works on <input> or <button> elements). Instead, it serves as an advisory hint to the browser about what kind of resource the link points to. The browser may use this information to adjust its UI — for example, showing a download prompt for application/pdf — but it is not required to act on it.
Because of this misunderstanding, developers sometimes write type="button" on an <a> element, thinking it will make the link behave like a button. The value button is not a valid MIME type (it has no subtype), so the validator flags it. If you need a button, use a <button> element instead. If you need a styled link that looks like a button, keep the <a> element and use CSS for styling.
Why this matters
- Standards compliance: The HTML specification requires the type attribute on <a> to be a valid MIME type string. An invalid value violates the spec and may be ignored by browsers or cause unexpected behavior.
- Accessibility and semantics: Using type="button" on a link can create confusion about the element’s role. Screen readers and assistive technologies rely on correct semantics to convey meaning to users.
- Browser behavior: While browsers are generally forgiving, an invalid type value provides no useful information and could interfere with how the browser handles the linked resource.
How to fix it
- If you intended to hint at the linked resource’s MIME type, make sure you provide a complete type/subtype value — for example, application/pdf rather than just application.
- If you used type to try to style or change the link’s behavior, remove the type attribute entirely. Use CSS for visual styling or switch to a more appropriate element like <button>.
- If you don’t need the type attribute, simply remove it. It’s entirely optional on <a> elements.
Examples
Incorrect: missing subtype
<a href="report.pdf" type="application">Download report</a>
The value application is incomplete — it’s missing the subtype portion after the slash.
Incorrect: not a MIME type at all
<a href="/order.php" type="button">Submit</a>
The value button is not a MIME type. This often stems from confusing the type attribute on <a> with the type attribute on <input> or <button>.
Correct: valid MIME type
<a href="report.pdf" type="application/pdf">Download report</a>
<a href="photo.jpeg" type="image/jpeg">See a photo</a>
The type attribute uses a properly formatted MIME type with both a type and subtype.
Correct: removing the attribute entirely
<a href="/order.php">Submit</a>
If the type attribute isn’t serving a real purpose, the simplest fix is to remove it.
Correct: using a button element instead
<button type="submit">Submit</button>
If you need actual button behavior (such as submitting a form), use a <button> element rather than an <a> element with an invalid type.
The type attribute on a <link> element specifies the MIME type of the linked resource. MIME types follow a specific format: a type and subtype separated by a single forward slash, like text/css, image/png, or application/json. They never contain the :// sequence found in URLs.
This error most commonly occurs when a URL is accidentally placed in the type attribute instead of in the href attribute, or when the attributes are confused with one another. For example, writing type="https://example.com/style.css" triggers this error because the validator encounters the colon in https: where it expects a valid MIME type token.
Another common cause is copying type values from other contexts (such as XML namespaces or schema references) that use URL-like strings, and mistakenly applying them to the type attribute.
Why this matters
- Standards compliance: The HTML specification requires the type attribute to contain a valid MIME type. Invalid values violate the spec and may cause browsers to misinterpret or ignore the linked resource.
- Browser behavior: Browsers use the type attribute as a hint for how to handle the resource. An invalid MIME type could lead the browser to skip loading the resource entirely, causing missing styles, icons, or other assets.
- Maintainability: Incorrect attribute values signal to other developers (and automated tools) that something is misconfigured, making the code harder to maintain.
How to fix it
- Check that type contains a valid MIME type, not a URL or other string. Common valid values include text/css, image/png, image/x-icon, image/svg+xml, and application/rss+xml.
- Ensure URLs are in the href attribute, not type.
- Consider removing type entirely. For stylesheets, modern browsers default to text/css, so type="text/css" is optional. For many use cases, the type attribute can be safely omitted.
Examples
❌ Incorrect: URL used as the type value
<link rel="stylesheet" type="https://example.com/style.css">
The validator sees the colon in https: and reports the error because this is a URL, not a MIME type.
❌ Incorrect: Attributes swapped
<link rel="icon" type="https://example.com/favicon.png" href="image/png">
Here the type and href values have been accidentally swapped.
✅ Correct: Valid MIME type with proper href
<link rel="icon" type="image/png" href="https://example.com/favicon.png">
✅ Correct: Stylesheet with valid type
<link rel="stylesheet" type="text/css" href="/css/style.css">
✅ Correct: Stylesheet without type (also valid)
<link rel="stylesheet" href="/css/style.css">
Since browsers default to text/css for stylesheets, omitting type is perfectly valid and keeps your markup cleaner.
✅ Correct: RSS feed link
<link rel="alternate" type="application/rss+xml" title="RSS Feed" href="/feed.xml">
A MIME type (also called a media type) is composed of two parts: a type and a subtype, separated by a forward slash (/) with no whitespace. For example, text/javascript has text as the type and javascript as the subtype. When you specify a value like text or javascript alone — without the slash and the other component — the validator reports this error because the subtype is missing.
This error commonly occurs when authors confuse the MIME type format with a simple label, writing something like type="text" or type="javascript" instead of the full type="text/javascript". It can also happen due to a typo, such as accidentally omitting the slash or the subtype portion.
Why this matters
Browsers rely on the type attribute to determine how to process the contents of a <script> element. An invalid MIME type can cause browsers to misinterpret or skip the script entirely. While modern browsers default to JavaScript when no type is specified, providing a malformed MIME type is not the same as omitting it — it may lead to unpredictable behavior across different browsers and versions. Keeping your markup valid also ensures better tooling support and forward compatibility.
How to fix it
You have two main options:
- Provide a complete, valid MIME type. For JavaScript, use text/javascript. For JSON data blocks, use application/json. For importmaps, use importmap.
- Remove the type attribute entirely. Per the HTML specification, the default type for <script> is text/javascript, so omitting type is perfectly valid and is actually the recommended approach for standard JavaScript.
Examples
Incorrect: missing subtype
<!-- "text" alone is not a valid MIME type -->
<script type="text" src="app.js"></script>
<!-- "javascript" alone is not a valid MIME type -->
<script type="javascript" src="app.js"></script>
Correct: full MIME type specified
<script type="text/javascript" src="app.js"></script>
Correct: omitting the type attribute (recommended for JavaScript)
<script src="app.js"></script>
Since text/javascript is the default, omitting the attribute is the cleanest approach for standard JavaScript files.
Correct: using type for non-JavaScript purposes
The type attribute is still useful when embedding non-JavaScript content in a <script> element. In these cases, always use the full MIME type:
<script type="application/json" id="config">
{"apiUrl": "https://example.com/api"}
</script>
<script type="importmap">
{ "imports": { "lodash": "/libs/lodash.js" } }
</script>
Common valid MIME types for <script>
| MIME Type | Purpose |
|---|---|
| text/javascript | Standard JavaScript (default) |
| module | JavaScript module |
| importmap | Import map |
| application/json | Embedded JSON data |
| application/ld+json | Linked Data / structured data |
Note that module and importmap are special values defined by the HTML specification and are not traditional MIME types, but they are valid values for the type attribute on <script> elements.
According to the HTML specification, the width and height attributes on <iframe> elements accept only a valid non-negative integer — a string of one or more ASCII digits (0–9) with no decimal points, spaces, or unit suffixes like px. This is different from CSS, where properties like width and height accept decimal values and units. The HTML attributes represent dimensions in CSS pixels implicitly, so only bare whole numbers are allowed.
When the W3C validator reports “Expected a digit but saw ‘.’ instead”, it means it was parsing the attribute value character by character and encountered a period (.) where only digits are valid. This typically happens when authors copy computed or fractional values from design tools, JavaScript calculations, or CSS into HTML attributes.
Why this matters
- Standards compliance: Browsers may handle invalid attribute values inconsistently. While most modern browsers will parse and truncate decimal values gracefully, the behavior is not guaranteed and falls outside the specification.
- Predictable rendering: Relying on how browsers handle malformed values can lead to subtle differences across browser engines. Using valid integers ensures consistent behavior everywhere.
- Code quality: Clean, valid markup is easier to maintain and signals professionalism, which matters especially for shared codebases and collaborative projects.
How to fix it
- Round the value to the nearest whole number. Use standard rounding rules: round up if the decimal portion is .5 or greater, round down otherwise.
- Remove any decimal point and trailing digits from the attribute value.
- If you need precise, fractional dimensions, use CSS instead of HTML attributes. CSS width and height properties accept decimal values with units (e.g., 602.88px).
Examples
❌ Invalid: decimal values in width and height
<iframe src="example.html" height="602.88" width="800.2"></iframe>
The validator will flag both attributes because 602.88 and 800.2 contain a . character.
✅ Fixed: whole number values
<iframe src="example.html" height="603" width="800"></iframe>
The decimal values have been rounded to the nearest integer: 602.88 becomes 603, and 800.2 becomes 800.
✅ Alternative: use CSS for precise dimensions
If you need exact fractional dimensions, move the sizing to CSS and remove the HTML attributes entirely:
<iframe src="example.html" style="height: 602.88px; width: 800.2px;"></iframe>
Or, better yet, use an external stylesheet:
<iframe src="example.html" class="content-frame"></iframe>
.content-frame {
width: 800.2px;
height: 602.88px;
}
❌ Invalid: other non-digit characters
This error can also appear if you include units in the attribute value:
<iframe src="example.html" width="800px" height="600px"></iframe>
✅ Fixed: remove the units
<iframe src="example.html" width="800" height="600"></iframe>
The same rule applies to the <img>, <video>, <canvas>, and other elements that accept width and height as HTML attributes — they all expect valid non-negative integers without decimals or units.
According to the HTML specification, the width and height attributes on img elements accept only valid non-negative integers. A valid non-negative integer consists of one or more ASCII digits (0–9) with no other characters — no decimal points, no spaces, no units like px. When the validator encounters a value such as 602.88, it parses the digits 602 successfully, then hits the . character where it expects another digit or the end of the value, triggering the error.
This issue commonly arises when dimension values are generated programmatically — for example, when a CMS, image processing tool, or JavaScript calculation produces floating-point numbers and outputs them directly into the HTML. It can also happen when copying dimension values from CSS or design tools that work in sub-pixel units.
Why this matters
- Standards compliance: The HTML specification is explicit that these attributes take integer values. Using decimals produces invalid markup.
- Unpredictable rendering: Browsers may handle the malformed value in different ways — some might truncate at the decimal point, others might ignore the attribute entirely. This can lead to layout shifts or incorrectly sized images.
- Layout stability: The width and height attributes are used by browsers to calculate the aspect ratio of an image before it loads, which helps prevent Cumulative Layout Shift (CLS). Invalid values can undermine this behavior, causing content to jump around as images load.
How to fix it
- Round to the nearest integer. If your value is 602.88, round it to 603. If it’s 800.2, round to 800.
- Remove the decimal point entirely. The value must contain only digits.
- Do not include units. Values like 800px are also invalid; use just 800.
- Fix the source of the values. If your CMS or build tool generates these attributes, update the logic to output integers (e.g., using Math.round() in JavaScript or round() in PHP/Python).
Examples
❌ Incorrect: decimal values in width and height
<img src="photo.jpg" alt="A golden retriever" height="602.88" width="800.2">
The validator reports errors for both attributes because . is not a valid character in a non-negative integer.
✅ Correct: whole number values
<img src="photo.jpg" alt="A golden retriever" height="603" width="800">
Both values are valid non-negative integers with no decimal points.
❌ Incorrect: trailing decimal point with no fractional part
<img src="banner.png" alt="Sale banner" width="1200." height="400.">
Even a trailing . with nothing after it is invalid — the parser still encounters an unexpected character.
✅ Correct: clean integer values
<img src="banner.png" alt="Sale banner" width="1200" height="400">
Using CSS for sub-pixel precision
If you genuinely need sub-pixel sizing (which is rare for images), use CSS instead of HTML attributes. CSS width and height properties do accept decimal values:
<img src="icon.svg" alt="Settings icon" style="width: 24.5px; height: 24.5px;">
However, keep in mind that you should still provide integer width and height HTML attributes for aspect ratio hinting, and then override with CSS if sub-pixel precision is needed:
<img
src="icon.svg"
alt="Settings icon"
width="25"
height="25"
style="width: 24.5px; height: 24.5px;">
This approach gives you valid HTML, proper aspect ratio hints for layout stability, and the precise sizing you need.
The HTML specification defines the width and height attributes on <embed> as accepting only valid non-negative integers. This means bare numbers like 600 or 800 that represent dimensions in CSS pixels. When you write width="100%", the validator expects a digit character but encounters the % sign, which doesn’t conform to the expected format.
This matters for several reasons. Browsers may interpret invalid attribute values inconsistently — some might ignore the percentage and fall back to a default size, while others might attempt to parse the number portion and discard the %. This leads to unpredictable rendering across different browsers and devices. Following the specification ensures your embedded content displays at predictable dimensions everywhere.
The same rule applies to the height attribute. Neither width nor height on <embed> supports units of any kind — no px, %, em, or other suffixes. Just a plain integer.
How to Fix It
You have two main approaches:
-
Use integer pixel values directly. Replace width="100%" with a specific pixel value like width="800". This is the simplest fix when you know the desired dimensions.
-
Use CSS for responsive or percentage-based sizing. Remove the width and height attributes (or set them to reasonable defaults) and apply CSS through a class, style attribute, or external stylesheet. This is the better approach when you need the embed to be fluid or responsive.
Examples
Invalid — percentage in the width attribute
This triggers the validator error because 100% is not a valid non-negative integer:
<embed src="file.pdf" type="application/pdf" width="100%" height="600">
Fixed — using pixel values in attributes
Replace the percentage with a plain integer:
<embed src="file.pdf" type="application/pdf" width="800" height="600">
Fixed — using CSS for percentage-based sizing
Remove the dimension attributes and use CSS to control the size:
<embed src="file.pdf" type="application/pdf" class="embed-fluid">
.embed-fluid {
width: 100%;
height: 600px;
}
Fixed — responsive embed with a wrapper container
For a fully responsive embed that maintains an aspect ratio, wrap it in a container and use CSS:
<div class="embed-wrapper">
<embed src="file.pdf" type="application/pdf">
</div>
.embed-wrapper {
width: 100%;
max-width: 960px;
aspect-ratio: 4 / 3;
}
.embed-wrapper embed {
width: 100%;
height: 100%;
}
This approach gives you full control over sizing and responsiveness without relying on invalid HTML attributes. The aspect-ratio property ensures the container (and therefore the embed) maintains consistent proportions as it scales.
The HTML specification defines the width and height attributes on <iframe> as accepting only valid non-negative integers. These values are interpreted as pixel dimensions. Unlike some older HTML practices where percentage values were sometimes accepted by browsers, the current standard does not permit the % character in these attributes. When the W3C validator encounters a value like "100%", it expects every character to be a digit and flags the % as invalid.
This is a standards compliance issue, but it also affects predictability across browsers. While most modern browsers may still interpret width="100%" on an <iframe> as you’d expect, this behavior is non-standard and not guaranteed. Relying on it means your layout could break in certain browsers or rendering modes. Using CSS for percentage-based sizing is the correct, reliable approach.
How to Fix It
If you need a fixed pixel width, simply provide the integer value without any unit:
<iframe src="page.html" width="600" height="400"></iframe>
If you need a percentage-based width (e.g., to make the iframe responsive), remove the width attribute entirely and use CSS instead. You can apply styles inline or through a stylesheet.
Inline style approach:
<iframe src="page.html" style="width: 100%; height: 400px;"></iframe>
CSS class approach:
<iframe src="page.html" class="responsive-iframe"></iframe>
.responsive-iframe {
width: 100%;
height: 400px;
}
This same rule applies to the height attribute — values like height="50%" are equally invalid and should be handled through CSS.
Examples
❌ Invalid: Percentage in width attribute
<iframe src="https://example.com" width="100%" height="300"></iframe>
This triggers the error because 100% is not a valid non-negative integer.
❌ Invalid: Percentage in both width and height
<iframe src="https://example.com" width="100%" height="50%"></iframe>
Both attributes contain invalid values due to the % character.
✅ Valid: Fixed pixel values using attributes
<iframe src="https://example.com" width="800" height="300"></iframe>
Both values are valid non-negative integers representing pixels.
✅ Valid: Percentage sizing using CSS
<iframe src="https://example.com" style="width: 100%; height: 300px;"></iframe>
The percentage is handled by CSS, and no invalid attributes are present.
✅ Valid: Responsive iframe with a wrapper
For a fully responsive iframe that maintains an aspect ratio, a common pattern uses a wrapper element:
<div style="position: relative; width: 100%; aspect-ratio: 16 / 9;">
<iframe
src="https://example.com"
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;">
</iframe>
</div>
This approach keeps the HTML valid while giving you full control over the iframe’s responsive behavior through CSS.
The HTML specification defines the width attribute on <img> elements as a “valid non-negative integer” — essentially a string of digits with no units, no decimals, and no percentage signs. When you write something like width="100%", the validator expects a digit character but encounters %, producing this error. While some older browsers historically accepted percentage values in the width attribute (a holdover from pre-HTML5 conventions), this was never part of the modern HTML standard and should not be relied upon.
This matters for several reasons. First, standards compliance ensures your markup behaves consistently across browsers and devices. Second, assistive technologies and browser layout engines may interpret an invalid width value unpredictably — some may ignore it entirely, others may parse it incorrectly. Third, the width and height attributes on <img> serve an important role in reserving layout space before the image loads (preventing Cumulative Layout Shift), but they only work correctly when set to valid integer pixel values that reflect the image’s intrinsic or intended pixel dimensions.
How to fix it
If you want a fixed pixel width, provide just the integer without any unit:
<img src="photo.jpg" alt="A red car" width="600">
If you need a percentage-based or responsive width, remove the width attribute from the HTML and use CSS instead. You can apply the style inline, via a <style> block, or in an external stylesheet.
If you want to preserve aspect ratio and prevent layout shift, keep the width and height attributes set to values that represent the image’s intrinsic aspect ratio (in pixels), and then override the displayed size with CSS.
Examples
❌ Invalid: percentage in the width attribute
<img src="photo.jpg" alt="A red car" width="100%">
This triggers the error because 100% is not a valid non-negative integer.
❌ Invalid: other non-integer values
<img src="photo.jpg" alt="A red car" width="50%">
<img src="banner.jpg" alt="Sale banner" width="300px">
<img src="icon.png" alt="Settings icon" width="2.5">
Units like px, percentage signs, and decimal points are all invalid in the width attribute.
✅ Fixed: using a pixel integer
<img src="photo.jpg" alt="A red car" width="800" height="600">
✅ Fixed: percentage width via inline CSS
<img src="photo.jpg" alt="A red car" style="width: 100%;">
✅ Fixed: responsive image with preserved aspect ratio
This approach sets the intrinsic dimensions in the attributes (to reserve layout space) and uses CSS to make the image responsive:
<style>
.responsive-img {
width: 100%;
height: auto;
}
</style>
<img src="photo.jpg" alt="A red car" width="800" height="600" class="responsive-img">
The browser uses the width and height attribute values to calculate the aspect ratio and reserve the correct amount of space, while CSS controls the actual rendered size. This is the recommended approach for responsive images because it avoids layout shift while still allowing flexible sizing.
The HTML specification defines the width attribute on <video> as a “valid non-negative integer,” which means it must consist only of digits (e.g., 640). It cannot include units like px, em, or %. When you write something like width="100%", the validator expects a digit character but encounters the % sign, producing this error.
This is a common mistake because CSS allows percentage values for width, and some older HTML elements (like <table>) historically accepted percentage values in their width attributes. However, the <video> element follows the modern HTML specification, which restricts width to pixel integers only.
Why this matters
- Standards compliance: Browsers may interpret invalid attribute values unpredictably. While most modern browsers might ignore the % and attempt to parse the number, this behavior is not guaranteed.
- Responsive design intent is lost: Even if a browser tries to handle width="100%", it may treat it as width="100" (100 CSS pixels), which is almost certainly not what you intended.
- Accessibility and consistency: Valid markup ensures assistive technologies and all browsers render your content as expected.
How to fix it
If you need a fixed pixel width, set the width attribute to a plain integer. If you need a responsive or percentage-based width, remove the width attribute entirely and use CSS.
Examples
❌ Invalid: percentage value in the width attribute
<video controls width="100%">
<source src="/media/video.mp4" type="video/mp4">
</video>
✅ Fixed: using a pixel integer for a fixed width
<video controls width="640" height="360">
<source src="/media/video.mp4" type="video/mp4">
</video>
✅ Fixed: using CSS for a percentage-based width
<video controls style="width: 100%;">
<source src="/media/video.mp4" type="video/mp4">
</video>
✅ Fixed: using an external stylesheet for responsive video
<style>
.responsive-video {
width: 100%;
max-width: 800px;
height: auto;
}
</style>
<video controls class="responsive-video">
<source src="/media/video.mp4" type="video/mp4">
</video>
The CSS approach is generally preferred for responsive layouts because it gives you much more control — you can combine width, max-width, and height: auto to create a video that scales proportionally within its container. The width and height HTML attributes are best used when you want to specify the video’s intrinsic dimensions in pixels, which also helps the browser reserve the correct amount of space before the video loads, reducing layout shifts.
According to the HTML Living Standard, the width and height attributes on the <object> element accept only valid non-negative integers — plain numbers representing pixels, such as 600 or 400. The validator expects each character in the value to be a digit (0–9). When it encounters a % sign, it reports “Expected a digit but saw ‘%’ instead.”
This is different from some legacy HTML 4 behavior where certain elements accepted percentage values in dimension attributes. In modern HTML, the <object> element’s dimension attributes are strictly pixel-only. The same restriction applies to elements like <img>, <video>, and <canvas>.
Why this matters
- Standards compliance: Browsers may still render percentage values in these attributes, but the behavior is not defined by the specification and cannot be relied upon across browsers or future versions.
- Predictable rendering: Pixel values in attributes give the browser a concrete intrinsic size for the object, which helps with layout calculations and prevents content reflow as the page loads.
- Accessibility and tooling: Assistive technologies and other tools that parse HTML rely on well-formed attribute values. Invalid values may cause unexpected behavior.
How to fix it
You have two options:
- Use pixel values in the attributes if you know the exact dimensions you need.
- Use CSS if you need percentage-based or responsive sizing. Remove the width and height attributes (or set them to pixel fallback values) and apply CSS width and height properties instead.
When using CSS for 100% height, remember that percentage heights require the parent elements to also have a defined height. This typically means setting height: 100% on html and body as well.
Examples
❌ Invalid: percentage values in attributes
<object data="example.pdf" type="application/pdf" width="100%" height="100%"></object>
The validator flags both width="100%" and height="100%" because % is not a digit.
✅ Fixed: pixel values in attributes
<object data="example.pdf" type="application/pdf" width="600" height="400"></object>
Plain integer values are valid and give the object a fixed size in pixels.
✅ Fixed: percentage sizing with CSS
<object
data="example.pdf"
type="application/pdf"
style="width: 100%; height: 500px;">
</object>
Using inline CSS allows you to mix units freely, including percentages, vh, em, and more.
✅ Fixed: full-page object with CSS
When you need the <object> to fill the entire viewport, use a stylesheet to set heights on the ancestor elements:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Full-Page Object Example</title>
<style>
html, body {
height: 100%;
margin: 0;
}
object {
display: block;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<object data="example.pdf" type="application/pdf"></object>
</body>
</html>
This approach is fully valid, responsive, and gives you much more control over sizing than HTML attributes alone.
Why This Matters
While HTML5 is quite permissive with id values (allowing almost anything except spaces), elements within XML-based vocabularies like SVG and MathML are held to stricter rules. When these elements appear in your HTML document, their attributes must still conform to XML 1.0 naming conventions as defined by the relevant specification.
XML 1.0 names must follow these rules:
- Must start with a letter (a–z, A–Z) or an underscore (_)
- Subsequent characters can be letters, digits (0–9), hyphens (-), underscores (_), and periods (.)
- Cannot contain spaces, colons (outside of namespaced contexts), or special characters like @, #, $, !, etc.
This error typically appears when design tools (such as Figma, Illustrator, or Sketch) export SVG files with auto-generated id values that include spaces or other invalid characters. Browsers may still render the content, but relying on non-conformant names can cause problems with CSS selectors, JavaScript’s getElementById(), URL fragment references, and accessibility tools that depend on valid identifiers.
How to Fix It
- Remove spaces — replace them with hyphens or underscores, or use camelCase.
- Ensure the name starts with a letter or underscore — if it starts with a digit, prefix it with a letter or underscore.
- Strip out special characters — remove or replace characters like @, #, (, ), etc.
- Review exported SVG files — if you’re embedding SVGs from design tools, clean up the generated id values before adding them to your HTML.
Examples
Invalid: Space in the id value
The space in "Group 270" makes this an invalid XML 1.0 name:
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<g id="Group 270">
<circle cx="50" cy="50" r="40" />
</g>
</svg>
Invalid: Name starts with a digit
XML 1.0 names cannot begin with a number:
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect id="1st-rectangle" width="100" height="50" />
</svg>
Invalid: Special characters in the name
Characters like ( and ) are not allowed:
<svg viewBox="0 0 200 100" xmlns="http://www.w3.org/2000/svg">
<path id="icon(home)" d="M10 80 L50 10 L90 80 Z" />
</svg>
Fixed: Valid XML 1.0 names
Replace spaces with hyphens, prefix digit-leading names with a letter, and remove special characters:
<svg viewBox="0 0 200 100" xmlns="http://www.w3.org/2000/svg">
<g id="group-270">
<circle cx="50" cy="50" r="40" />
</g>
<rect id="first-rectangle" width="100" height="50" />
<path id="icon-home" d="M10 80 L50 10 L90 80 Z" />
</svg>
Tip: Cleaning up exported SVGs
Design tools often produce id values like "Frame 42", "Vector (Stroke)", or "123_layer". A quick find-and-replace workflow can fix these before they land in your codebase. You can also use tools like SVGO to optimize and clean up SVG output, including stripping or renaming invalid identifiers.
The <area> element defines a clickable region within an image map (<map>). The coords attribute works together with the shape attribute to describe the geometry of that region. When the coordinates don’t conform to the rules for the given shape, the browser may ignore the area entirely or interpret it unpredictably, making the clickable region inaccessible to users.
Each shape type has strict requirements:
- Rectangle (shape="rect"): Requires exactly four integers in the format x1,y1,x2,y2, where x1,y1 is the top-left corner and x2,y2 is the bottom-right corner. Because 0,0 is the top-left of the image, x1 must be less than x2 and y1 must be less than y2.
- Circle (shape="circle"): Requires exactly three integers in the format x,y,r, where x,y is the center of the circle and r is the radius. The radius must be a positive integer, and the first coordinate (the x-center) must be less than the third value (the radius) is not required—but the validator message mentions this constraint to flag cases where values appear swapped or malformed.
- Polygon (shape="poly"): Requires at least six integers (three x,y coordinate pairs), forming a polygon with at least three vertices (a triangle). The format is x1,y1,x2,y2,...,xn,yn, and the number of integers must be even since they represent pairs.
Getting these formats wrong is a standards compliance issue. Assistive technologies such as screen readers rely on valid <area> definitions to convey interactive regions to users. Invalid coordinates can also cause the clickable area to silently fail in some browsers.
Examples
Invalid: Rectangle with swapped coordinates
The top-left corner values are larger than the bottom-right corner values:
<map name="nav">
<area shape="rect" coords="200,150,50,10" href="/home" alt="Home">
</map>
Fixed: Rectangle with correct coordinate order
<map name="nav">
<area shape="rect" coords="50,10,200,150" href="/home" alt="Home">
</map>
Invalid: Circle with wrong number of values
Four values are provided instead of the required three:
<map name="nav">
<area shape="circle" coords="100,75,50,25" href="/info" alt="Info">
</map>
Fixed: Circle with three values
<map name="nav">
<area shape="circle" coords="100,75,50" href="/info" alt="Info">
</map>
Invalid: Polygon with too few coordinates
Only four integers (two coordinate pairs) are provided, but a polygon needs at least three pairs:
<map name="nav">
<area shape="poly" coords="10,20,30,40" href="/about" alt="About">
</map>
Fixed: Polygon with at least three coordinate pairs
<map name="nav">
<area shape="poly" coords="10,20,30,40,20,60" href="/about" alt="About">
</map>
Invalid: Non-integer or malformed values
Decimal numbers and spaces in the wrong places will also trigger this error:
<map name="nav">
<area shape="rect" coords="10.5, 20, 100, 200" href="/page" alt="Page">
</map>
Fixed: Using only comma-separated integers
<map name="nav">
<area shape="rect" coords="10,20,100,200" href="/page" alt="Page">
</map>
Complete valid image map example
<img src="floorplan.png" alt="Office floor plan" usemap="#office">
<map name="office">
<area shape="rect" coords="0,0,150,100" href="/lobby" alt="Lobby">
<area shape="circle" coords="200,150,40" href="/meeting-room" alt="Meeting room">
<area shape="poly" coords="300,50,400,50,400,150,350,200,300,150" href="/lounge" alt="Lounge">
</map>
When debugging coordinate issues, double-check that the shape attribute matches the number of coordinates you’ve provided, that all values are non-negative integers separated by commas with no extra spaces, and that rectangle corners are specified in the correct top-left to bottom-right order.
In HTML, the width and height attributes on elements like <img> and <iframe> are defined as accepting only valid non-negative integers. According to the HTML specification, the value is implicitly in CSS pixels, so appending px or any other unit is both unnecessary and invalid. The parser expects every character in the value to be a digit (0–9), and when it encounters a letter like p, it reports the error.
This is a common mistake, especially for developers who frequently work with CSS, where px units are required. In HTML attributes, however, the convention is different — the pixel unit is implied, and adding it creates a malformed value. Browsers may still attempt to parse the number by ignoring the trailing characters, but this behavior is not guaranteed and should not be relied upon.
Getting these attributes right matters for several reasons:
- Standards compliance ensures your markup is predictable and portable across all browsers and user agents.
- Layout stability depends on the browser correctly reading width and height to reserve space for images and iframes before they load, preventing cumulative layout shift (CLS). A malformed value could cause the browser to fall back to default sizing or ignore the attribute entirely.
- Accessibility tools and screen readers may use these attributes to convey information about embedded content, and invalid values could interfere with that process.
If you need to set dimensions using units other than pixels (such as percentages or viewport units), use CSS instead of HTML attributes.
Examples
❌ Invalid: using px in the attribute value
<img src="cat.jpg" alt="A cat sitting on a windowsill" width="225px" height="100px">
The validator reports an error because 225px and 100px contain the non-digit characters px.
✅ Valid: plain integers without units
<img src="cat.jpg" alt="A cat sitting on a windowsill" width="225" height="100">
❌ Invalid: using percentage in the attribute value
<iframe src="embed.html" width="100%" height="400px" title="Embedded content"></iframe>
Both 100% and 400px are invalid because they contain non-digit characters.
✅ Valid: plain integers on an <iframe>
<iframe src="embed.html" width="800" height="400" title="Embedded content"></iframe>
✅ Using CSS when you need non-pixel units
If you need percentage-based or responsive sizing, apply it through CSS rather than HTML attributes:
<iframe src="embed.html" style="width: 100%; height: 400px;" title="Embedded content"></iframe>
Or better yet, use an external stylesheet:
<style>
.responsive-frame {
width: 100%;
height: 400px;
}
</style>
<iframe src="embed.html" class="responsive-frame" title="Embedded content"></iframe>
Quick reference of invalid vs. valid values
| Invalid value | Problem | Valid alternative |
|---|---|---|
| 225px | Contains px | 225 |
| 100% | Contains % | Use CSS instead |
| 20em | Contains em | Use CSS instead |
| auto | Not a number | Use CSS instead |
| 10.5 | Decimal point | 10 or 11 |
The fix is straightforward: strip any unit suffixes from width and height HTML attributes and provide plain integer values. For anything beyond simple pixel dimensions, move your sizing logic to CSS.
The language tag yaml is reserved by IANA and cannot be used as a value for the lang attribute, which expects a valid BCP 47 language tag (like en for English or fr for French).
The lang attribute specifies the natural language of an element’s content — human languages like English, Spanish, or Japanese. It is not meant to indicate a programming or markup language. When you write lang="yaml" on a <code> element, the validator rejects it because yaml is a reserved IANA subtag with no valid use in BCP 47.
If your goal is to identify the code language for syntax highlighting or styling purposes, use the class attribute instead. A common convention, recommended by the HTML specification itself, is to use a class prefixed with language-, such as class="language-yaml".
HTML Examples
❌ Invalid: using lang for code language
<pre>
<code lang="yaml">
name: my-project
version: 1.0.0
</code>
</pre>
✅ Valid: using class for code language
<pre>
<code class="language-yaml">
name: my-project
version: 1.0.0
</code>
</pre>
This class="language-*" convention is widely supported by syntax highlighting libraries like Prism.js and highlight.js.
The HTML specification defines a specific set of valid values for the type attribute on <input> elements, including text, number, email, tel, url, date, password, search, hidden, checkbox, radio, file, submit, reset, button, image, range, color, and others. The value "zip" is not among them. When a browser encounters an unrecognized type value, it falls back to type="text" — so the input may appear to work, but the markup is invalid and you lose the opportunity to leverage built-in browser features for better user experience.
This matters for several reasons. Invalid HTML can cause unpredictable behavior across different browsers and assistive technologies. Screen readers and other tools rely on valid markup to convey the purpose of form controls to users. Additionally, using the correct combination of valid attributes allows browsers to show optimized keyboards on mobile devices (e.g., a numeric keypad for ZIP codes) and to autofill values intelligently.
For ZIP or postal code fields, the best approach is to use type="text" combined with the autocomplete="postal-code" attribute, which tells browsers exactly what kind of data is expected. You can further enhance the input with inputmode="numeric" to trigger a numeric keyboard on mobile devices (for purely numeric ZIP codes like in the US) and a pattern attribute for client-side validation.
Examples
❌ Invalid: Using type="zip"
<label for="zip">ZIP Code</label>
<input type="zip" id="zip" name="zip">
This triggers the validation error because "zip" is not a valid value for the type attribute.
✅ Valid: Using type="text" with appropriate attributes (US ZIP code)
<label for="zip">ZIP Code</label>
<input
type="text"
id="zip"
name="zip"
inputmode="numeric"
pattern="[0-9]{5}(-[0-9]{4})?"
autocomplete="postal-code"
placeholder="12345"
aria-describedby="zip-hint">
<span id="zip-hint">5-digit ZIP code (e.g., 12345 or 12345-6789)</span>
This approach uses type="text" to remain valid, inputmode="numeric" to prompt a numeric keyboard on mobile, pattern for client-side format validation, and autocomplete="postal-code" so browsers can autofill the field correctly.
✅ Valid: International postal code field
<label for="postal">Postal Code</label>
<input
type="text"
id="postal"
name="postal_code"
autocomplete="postal-code">
For international postal codes that may contain letters (e.g., UK, Canada), omit inputmode="numeric" and use a broader or no pattern, since formats vary widely by country.
Why not type="number"?
You might be tempted to use type="number" for ZIP codes, but this is discouraged. type="number" is designed for values that represent a quantity — it may strip leading zeros (turning “01234” into “1234”), add increment/decrement spinner buttons, and behave unexpectedly with non-numeric postal codes. Always use type="text" for ZIP and postal codes.
The HTML parser has specific rules for how it handles sequences that begin with <. When it encounters <! followed by something other than -- (which starts a comment) or DOCTYPE (case-insensitive), the parser doesn’t know how to interpret it. According to the WHATWG HTML Living Standard, such sequences are treated as “bogus comments” — the parser will try to recover by consuming content until it finds a > character, treating everything in between as a comment node. While browsers handle this gracefully through error recovery, the underlying markup is invalid and may not behave as intended.
Several common patterns trigger this error:
- Malformed comment delimiters: Adding a space between <! and --, using only one hyphen (<!- comment ->), or forgetting the closing -- before >.
- Stray <! sequences: Accidentally typing <! in your markup without a valid keyword following it, such as <!something>.
- XML processing instructions: Using <?xml version="1.0"?> or similar <?...?> syntax in an HTML document. Processing instructions are valid in XML/XHTML but are treated as bogus comments in HTML.
- Mistyped doctype: Writing something like <!DOCKTYPE html> instead of <!DOCTYPE html>.
- Template or server-side artifacts: Server-side code or templating engines sometimes output fragments like <!--> or <![]> that the HTML parser cannot interpret.
This matters for several reasons. First, since the parser consumes everything up to the next > as a bogus comment, actual content or markup could be swallowed and hidden from the rendered page. Second, different parsers may recover from these errors in slightly different ways, leading to inconsistent rendering. Third, invalid markup can interfere with assistive technologies that rely on a well-formed DOM.
To fix the issue, locate the flagged line in your HTML source and ensure that:
- All comments begin with exactly <!-- (no spaces or missing hyphens) and end with exactly -->.
- Your <!DOCTYPE html> declaration is correctly spelled.
- You haven’t included XML processing instructions (<?...?>) in an HTML document.
- No stray <! or <? characters appear in your markup.
Examples
Malformed comment delimiter
<!-- ❌ Space between <! and -- -->
<! -- This is not a valid comment -->
<!-- ❌ Single hyphen instead of double -->
<!- This is not valid either ->
<!-- ✅ Correct comment syntax -->
<!-- This is a valid comment -->
XML processing instruction in HTML
<!-- ❌ Processing instructions are not valid in HTML -->
<?xml version="1.0" encoding="UTF-8"?>
<p>Hello</p>
<!-- ✅ Remove the processing instruction; it's not needed in HTML -->
<p>Hello</p>
Mistyped DOCTYPE
<!-- ❌ Misspelled DOCTYPE triggers bogus comment -->
<!DOCKTYPE html>
<!-- ✅ Correct spelling -->
<!DOCTYPE html>
Stray <! sequence
<!-- ❌ Invalid use of <! -->
<!if condition>
<p>Conditional content</p>
<!endif>
<!-- ✅ Use standard HTML comments for conditional notes -->
<!-- condition: start -->
<p>Conditional content</p>
<!-- condition: end -->
Empty or broken comment
<!-- ❌ Incomplete comment syntax -->
<!>
<p>Content</p>
<!-- ❌ Another broken variant -->
<!--->
<p>Content</p>
<!-- ✅ Either remove it or write a proper comment -->
<!-- placeholder -->
<p>Content</p>
Conditional comments (legacy IE syntax)
Conditional comments like <!--[if IE]> were a proprietary feature of Internet Explorer. While they don’t typically trigger a bogus comment error (since they start with <!--), related patterns like <![if IE]> (without the --) will. Since IE conditional comments are no longer supported by any modern browser, the best fix is to remove them entirely.
<!-- ❌ Non-comment conditional syntax -->
<![if IE]>
<link rel="stylesheet" href="ie.css">
<![endif]>
<!-- ✅ Remove legacy conditional comments -->
<link rel="stylesheet" href="styles.css">
What Are Control Characters?
Control characters occupy code points U+0000 through U+001F and U+007F through U+009F in Unicode. They were originally designed for controlling hardware devices (e.g., U+0002 is “Start of Text,” U+0007 is “Bell,” U+001B is “Escape”). These characters have no visual representation and carry no semantic meaning in a web document.
The HTML specification explicitly forbids character references that resolve to most control characters. Even though the syntax  is a structurally valid character reference, the character it points to is not a permissible content character. The W3C validator raises this error to flag references like �, , , , and others that fall within the control character ranges.
Why This Is a Problem
- Standards compliance: The WHATWG HTML Living Standard defines a specific set of “noncharacter” and “control character” code points that must not be referenced. Using them produces a parse error.
- Unpredictable rendering: Browsers handle illegal control characters inconsistently. Some may silently discard them, others may render a replacement character (�), and others may exhibit unexpected behavior.
- Accessibility: Screen readers and other assistive technologies may choke on or misinterpret control characters, degrading the experience for users who rely on these tools.
- Data integrity: Control characters in your markup often indicate a copy-paste error, a corrupted data source, or a templating bug that inserts raw binary data into HTML output.
How to Fix It
- Identify the offending reference — look for character references like , , �, , or similar that point to control character code points.
- Determine intent — figure out what character or content was actually intended. Often, a control character reference is the result of a bug in a data pipeline or template engine.
- Remove or replace — either delete the reference entirely or replace it with the correct printable character or HTML entity.
Examples
Incorrect: Control character reference
This markup contains , which expands to the control character U+0002 (Start of Text) and triggers the validation error:
<p>Some text  more text</p>
Incorrect: Hexadecimal form of a control character
The same problem occurs with the hexadecimal syntax:
<p>Data: </p>
Correct: Remove the control character reference
If the control character was unintentional, simply remove it:
<p>Some text more text</p>
Correct: Use a valid character reference instead
If you intended to display a special character, use the correct printable code point or named entity. For example, to display a bullet (•), copyright sign (©), or ampersand (&):
<p>Item • Details</p>
<p>Copyright © 2024</p>
<p>Tom & Jerry</p>
Correct: Full document without control characters
<!DOCTYPE html>
<html lang="en">
<head>
<title>Example Page</title>
</head>
<body>
<p>This paragraph uses only valid character references: & < > ©</p>
</body>
</html>
Common Control Character Code Points to Avoid
| Reference | Code Point | Name |
|---|---|---|
| � | U+0000 | Null |
|  | U+0001 | Start of Heading |
|  | U+0002 | Start of Text |
|  | U+0007 | Bell |
|  | U+0008 | Backspace |
|  | U+000B | Vertical Tab |
|  | U+000C | Form Feed |
|  | U+007F | Delete |
If your content is generated dynamically (from a database, API, or user input), sanitize the data before inserting it into HTML to strip out control characters. Most server-side languages and templating engines provide utilities for this purpose.
Character references are how HTML represents special characters that would otherwise be interpreted as markup or that aren’t easily typed on a keyboard. They come in three forms:
- Named references like &, <, ©
- Decimal numeric references like <, ©
- Hexadecimal numeric references like <, ©
All three forms share the same structure: they begin with & and must end with ;. When you omit the trailing semicolon, the HTML parser enters error recovery mode. Depending on the context, it may still resolve the reference (browsers are lenient), but this behavior is not guaranteed and varies across situations. For example, © without a semicolon might still render as ©, but ¬it could be misinterpreted as the ¬ (¬) reference followed by it, producing unexpected output like “¬it” instead of the literal text “¬it”.
Why this matters
- Unpredictable rendering: Without the semicolon, browsers use heuristic error recovery that can produce different results depending on surrounding text. What looks fine today might break with different adjacent characters.
- Standards compliance: The WHATWG HTML specification requires the semicolon terminator. Omitting it is a parse error.
- Maintainability: Other developers (or future you) may not realize the ampersand was intended as a character reference, making the code harder to read and maintain.
- Data integrity: In URLs within href attributes, a missing semicolon on a character reference can corrupt query parameters and produce broken links.
How to fix it
- Add the missing semicolon to the end of every character reference.
- If you meant a literal ampersand, use & instead of a bare &. This is especially common in URLs with query strings.
- Search your document for patterns like &something without a trailing ; to catch all instances.
Examples
❌ Missing semicolon on named references
<p>5 < 10 and 10 > 5</p>
<p>© 2024 All rights reserved</p>
✅ Properly terminated named references
<p>5 < 10 and 10 > 5</p>
<p>© 2024 All rights reserved</p>
❌ Missing semicolon on numeric references
<p>The letter A: A</p>
<p>Hex example: A</p>
✅ Properly terminated numeric references
<p>The letter A: A</p>
<p>Hex example: A</p>
❌ Bare ampersand in a URL (common mistake)
<a href="https://example.com/search?name=alice&age=30">Search</a>
Here the validator sees &age and tries to interpret it as a character reference without a semicolon.
✅ Escaped ampersand in a URL
<a href="https://example.com/search?name=alice&age=30">Search</a>
❌ Ambiguous reference causing wrong output
<p>The entity ¬it; doesn't exist, but ¬ without a semicolon resolves to ¬</p>
✅ Use & when you want a literal ampersand
<p>The text &notit is displayed literally when properly escaped.</p>
A quick rule of thumb: every & in your HTML should either be the start of a complete, semicolon-terminated character reference, or it should itself be written as &.
When you use the W3C Markup Validation Service by submitting a URL (rather than uploading a file or pasting code directly), the validator attempts to fetch the page from your server over the internet. If the server doesn’t respond within a set period, the connection times out and the validator reports this error instead of any HTML validation results.
This issue is entirely network- or server-related and has nothing to do with the quality of your HTML markup. However, it prevents you from validating your code, so it’s worth resolving or working around.
Common Causes
There are several reasons the validator may fail to connect:
- Server is offline or unresponsive. The web server hosting your site may be down, overloaded, or restarting.
- Firewall or security rules blocking the validator. Some server configurations, Web Application Firewalls (WAFs), or hosting providers block automated requests. The W3C Validator identifies itself via its User-Agent header, and some security tools may reject it.
- The URL is not publicly accessible. If your site is on localhost, behind a VPN, on an intranet, or restricted by IP allowlisting, the validator cannot reach it.
- DNS issues. The domain name may not resolve correctly from the validator’s network, even if it works from your machine.
- SSL/TLS misconfiguration. If the site uses HTTPS but has an expired certificate, a self-signed certificate, or an incomplete certificate chain, the connection may fail or be refused.
- Slow server response. If your page takes a very long time to generate (e.g., a complex database query), the validator may time out before receiving a response.
- Cloudflare or CDN challenge pages. Services like Cloudflare may present a bot-detection challenge or CAPTCHA to the validator, preventing it from fetching the actual page.
How to Fix It
1. Verify your site is publicly reachable
Test that your URL is accessible from outside your local network. You can use tools like curl from a remote server or an online service like “Down For Everyone Or Just Me.”
curl -I https://example.com
If this returns an HTTP status code like 200 OK, the server is responding.
2. Check your firewall and security rules
Make sure your server or hosting provider isn’t blocking the W3C Validator’s requests. The validator’s User-Agent string typically contains W3C_Validator. If you use a WAF or bot-protection service, add an exception for the validator.
3. Fix SSL/TLS issues
If your site uses HTTPS, verify your certificate is valid and the chain is complete. You can test this with tools like SSL Labs.
4. Use an alternative validation method
If you can’t make your site publicly accessible to the validator (e.g., it’s a staging server or a local development environment), you can bypass the network requirement entirely:
- Direct input: Copy your page’s HTML source and paste it into the validator’s “Validate by Direct Input” tab at validator.w3.org.
- File upload: Save the HTML file locally and use the “Validate by File Upload” tab.
- View source, then paste: In your browser, view the page source (Ctrl+U or Cmd+U), copy the full HTML, and paste it into the validator.
5. Reduce server response time
If your server is online but slow, optimize the page so it responds faster. The validator expects a response within a reasonable timeout window. Consider caching, reducing database queries, or simplifying server-side processing for the page you’re trying to validate.
Examples
Validating a local development site (will fail)
Submitting a URL like this to the W3C Validator will time out because the validator cannot access your local machine:
http://localhost:3000/index.html
http://192.168.1.50/mysite/
http://my-dev-machine.local/page.html
Workaround: Validate by direct input
Instead, copy your HTML and paste it directly. For example, if your page contains:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<h1>Hello, world!</h1>
<p>This is my page.</p>
</body>
</html>
Paste this into the “Validate by Direct Input” field on the W3C Validator. This completely avoids the network connection and lets you validate your markup regardless of server accessibility.
The lang attribute on the <html> element sets the default language for all text content within the page. Without it, assistive technologies like screen readers have to guess which language the content is in, which can lead to garbled or incorrectly pronounced text. For example, a French screen reader attempting to read English text — or vice versa — produces a poor experience for users who rely on these tools.
Beyond accessibility, the lang attribute matters for several other reasons:
- Search engines use it to serve the correct language version of your page in search results.
- Browsers rely on it to choose appropriate fonts, hyphenation rules, and quotation mark styles.
- Translation tools use it to detect the source language of the page.
- CSS selectors like :lang() depend on it to apply language-specific styling.
The value of the lang attribute must be a valid BCP 47 language tag. Common examples include en (English), fr (French), es (Spanish), de (German), zh (Chinese), ja (Japanese), and ar (Arabic). You can also be more specific with region subtags, such as en-US for American English or pt-BR for Brazilian Portuguese.
If your page contains sections in a different language than the primary one, you can use the lang attribute on individual elements to override the document-level language for that section.
Examples
Missing lang attribute (triggers the warning)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
Fixed with lang attribute
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
Using a region subtag for specificity
<!DOCTYPE html>
<html lang="en-GB">
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
<body>
<p>Colour is spelt differently here.</p>
</body>
</html>
Overriding the language for a specific section
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Multilingual Page</title>
</head>
<body>
<p>This paragraph is in English.</p>
<p lang="fr">Ce paragraphe est en français.</p>
</body>
</html>
In this last example, the document language is English, but the second paragraph is marked as French. A screen reader will switch to French pronunciation rules for that paragraph, then revert to English for the rest of the page.
When you set user-scalable=no in your viewport meta tag, the browser completely disables pinch-to-zoom and other scaling gestures on mobile devices. Similarly, setting maximum-scale=1 (or any low value) caps how far a user can zoom in, effectively locking them out of enlarging content. While developers sometimes use these values to create an “app-like” experience or prevent layout issues during zoom, they directly violate accessibility best practices.
Why this is a problem
Accessibility
The Web Content Accessibility Guidelines (WCAG) Success Criterion 1.4.4 (Resize Text) requires that text can be resized up to 200% without loss of content or functionality. Preventing zoom makes it impossible for users with low vision, cognitive disabilities, or motor impairments to interact comfortably with your page. Many users depend on pinch-to-zoom as their primary way to read content on mobile devices.
Standards compliance
The W3C HTML Validator flags this as a warning because it conflicts with established accessibility standards. While it won’t cause your page to fail validation outright, it signals a practice that harms usability. Modern browsers and operating systems have also started to override restrictive viewport settings in some cases — for example, iOS Safari ignores user-scalable=no by default — which means the restriction may not even work as intended while still triggering warnings.
User experience
Even for users without disabilities, preventing zoom can be frustrating. Small text, dense layouts, or content that doesn’t quite fit a screen size can all benefit from the user being able to zoom in. Restricting this capability removes a fundamental browser feature that users expect.
How to fix it
- Remove user-scalable=no from your viewport meta tag. If present, either delete it or set it to yes.
- Remove or increase maximum-scale. If you need to set it, use a value of 5 or higher. Ideally, remove it entirely and let the browser handle zoom limits.
- Remove minimum-scale if it’s set to 1, as this can also restrict zoom behavior on some browsers when combined with other values.
- Test your layout at various zoom levels to ensure content reflows properly and remains usable.
Examples
❌ Viewport that prevents zooming
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
This completely disables user zoom on supporting browsers.
❌ Viewport with restrictive maximum-scale
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0">
This caps zoom at 100%, effectively preventing any meaningful zoom.
❌ Both restrictions combined
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
This is the most restrictive combination and is commonly seen in mobile-first frameworks and templates.
✅ Accessible viewport (recommended)
<meta name="viewport" content="width=device-width, initial-scale=1">
This sets a responsive viewport without restricting zoom at all. The browser’s default zoom behavior is preserved, and users can scale freely.
✅ Accessible viewport with a generous maximum-scale
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5">
If you have a specific reason to set maximum-scale, use a value of 5 or higher. This still allows substantial zoom while giving you some control over extreme zoom levels.
✅ Full document example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Accessible Page</title>
</head>
<body>
<h1>Welcome</h1>
<p>This page allows users to zoom freely.</p>
</body>
</html>
If your layout breaks when users zoom in, the solution is to fix the CSS — using relative units like em, rem, or percentages, and responsive design techniques — rather than disabling zoom. A well-built responsive layout should handle zoom gracefully without needing to restrict it.
The HTML heading elements <h1> through <h6> define a document’s heading hierarchy. The <h1> element represents the highest-level heading, and each subsequent level (<h2>, <h3>, etc.) represents a deeper subsection. This hierarchy is critical for both accessibility and document structure.
The HTML5 specification once introduced a “document outline algorithm” that would have allowed multiple <h1> elements to be automatically scoped by their parent sectioning elements (<section>, <article>, <nav>, <aside>). Under this model, an <h1> inside a nested <section> would be treated as a lower-level heading. However, no browser or assistive technology ever implemented this algorithm. The outline algorithm was eventually removed from the WHATWG HTML specification. In practice, screen readers and other tools treat every <h1> on a page as a top-level heading, regardless of nesting.
This matters for several reasons:
- Accessibility: Screen reader users frequently navigate by headings to get an overview of a page’s content. When multiple <h1> elements exist, the heading list becomes flat and unclear, making it difficult to understand the page’s structure and find specific content.
- SEO: Search engines use heading hierarchy to understand page structure and content importance. Multiple <h1> elements can dilute the semantic signal of your primary page topic.
- Standards compliance: While using multiple <h1> elements is not a validation error, the W3C validator raises this as a warning because it is widely considered a best practice to reserve <h1> for the single, top-level page heading.
To fix this warning, follow these guidelines:
- Use exactly one <h1> per page to describe the main topic or title.
- Use <h2> for major sections beneath it, <h3> for subsections within those, and so on.
- Don’t skip heading levels (e.g., jumping from <h1> to <h3> without an <h2>).
Examples
Incorrect: Multiple <h1> elements
This example uses <h1> inside each sectioning element, which triggers the warning. Screen readers will present all three headings at the same level, losing the intended hierarchy.
<h1>My Blog</h1>
<section>
<h1>Latest Posts</h1>
<article>
<h1>How to Write Accessible HTML</h1>
<p>Writing semantic HTML is important for accessibility.</p>
</article>
<article>
<h1>Understanding CSS Grid</h1>
<p>CSS Grid makes complex layouts straightforward.</p>
</article>
</section>
Correct: Proper heading hierarchy
Use a single <h1> for the page title and nest subsequent headings using the appropriate levels.
<h1>My Blog</h1>
<section>
<h2>Latest Posts</h2>
<article>
<h3>How to Write Accessible HTML</h3>
<p>Writing semantic HTML is important for accessibility.</p>
</article>
<article>
<h3>Understanding CSS Grid</h3>
<p>CSS Grid makes complex layouts straightforward.</p>
</article>
</section>
Incorrect: <h1> nested inside a section without a parent heading
Even a single <h1> nested deeply inside sectioning content can trigger this warning if the structure suggests it is not the page’s primary heading.
<section class="about">
<article>
<h1>Article heading</h1>
<p>Lorem ipsum dolor sit amet.</p>
</article>
</section>
Correct: Section with its own heading and properly ranked article heading
<section class="about">
<h1>About</h1>
<article>
<h2>Article heading</h2>
<p>Lorem ipsum dolor sit amet.</p>
</article>
</section>
Correct: Full page structure with clear heading hierarchy
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Company Homepage</title>
</head>
<body>
<header>
<h1>Acme Corporation</h1>
</header>
<main>
<section>
<h2>Our Services</h2>
<h3>Web Development</h3>
<p>We build modern, accessible websites.</p>
<h3>Design</h3>
<p>Our design team creates beautiful interfaces.</p>
</section>
<section>
<h2>About Us</h2>
<p>We have been in business since 2005.</p>
</section>
</main>
</body>
</html>
In this structure, screen readers will present a clear, navigable outline: one top-level heading followed by properly nested subheadings that reflect the logical organization of the content.
Multiple h1 elements on a page can confuse screen readers and other assistive tools, which treat every h1 as the top-level heading.
HTML headings (h1 through h6) form an outline of your document. The h1 element represents the highest-level heading, and most accessibility guidelines recommend using only one h1 per page. When screen readers encounter multiple h1 elements, they may present them all as equally important top-level sections, making it harder for users to understand the page structure.
Instead of using multiple h1 elements, use a proper heading hierarchy. Start with a single h1 for the main topic of the page, then use h2 for major sections, h3 for subsections, and so on. This creates a clear, navigable document outline.
The W3C warning also mentions a headingoffset attribute, which is a proposed feature for <section> elements that would allow automatic heading level adjustment. However, this attribute is not yet implemented in any browser, so you should not rely on it.
Example with the issue
<body>
<h1>My Website</h1>
<section>
<h1>About Us</h1>
<p>Some content here.</p>
</section>
<section>
<h1>Contact</h1>
<p>More content here.</p>
</section>
</body>
Example with proper heading hierarchy
<body>
<h1>My Website</h1>
<section>
<h2>About Us</h2>
<p>Some content here.</p>
</section>
<section>
<h2>Contact</h2>
<p>More content here.</p>
</section>
</body>
Keep one h1 per page and nest subsequent headings using h2 through h6 to reflect the logical structure of your content. This approach is well-supported across all browsers and assistive technologies today.
Content Security Policy (CSP) is a security mechanism that lets you control which resources a browser is allowed to load for your page. When defined via a <meta http-equiv="Content-Security-Policy"> tag, the validator checks whether the content attribute contains a well-formed policy. If the policy string contains unrecognized directives, malformed source expressions, or syntax errors, the validator reports “Bad content security policy.”
Common causes of this error include:
- Misspelled directive names — e.g., script-scr instead of script-src.
- Invalid source values — e.g., using self without single quotes (it must be 'self').
- Using directives not allowed in <meta> tags — the frame-ancestors, report-uri, and sandbox directives are not supported when CSP is delivered via a <meta> element.
- Incorrect separators — directives are separated by semicolons (;), not commas or pipes.
- Missing or extra quotes — keywords like 'none', 'self', 'unsafe-inline', and 'unsafe-eval' must be wrapped in single quotes. Conversely, hostnames and URLs must not be quoted.
This matters because a malformed CSP may be silently ignored by browsers, leaving your site without the intended protection against cross-site scripting (XSS) and data injection attacks. Even a small typo can cause an entire directive to be skipped, creating a security gap you might not notice.
How to fix it
- Check directive names against the CSP specification. Valid fetch directives include default-src, script-src, style-src, img-src, font-src, connect-src, media-src, object-src, child-src, worker-src, and others.
- Wrap keyword values in single quotes: 'self', 'none', 'unsafe-inline', 'unsafe-eval', and nonce/hash sources like 'nonce-abc123'.
- Separate directives with semicolons. Multiple source values within a single directive are separated by spaces.
- Avoid directives that are invalid in <meta> tags. If you need frame-ancestors or report-uri, deliver CSP via an HTTP header instead.
- Don’t include the header name inside the content attribute. The content value should contain only the policy itself.
Examples
❌ Misspelled directive and unquoted keyword
<meta http-equiv="Content-Security-Policy"
content="default-src self; script-scr https://example.com">
Here, self is missing its required single quotes, and script-scr is a typo for script-src.
✅ Corrected directive name and properly quoted keyword
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src https://example.com">
❌ Using a directive not allowed in a <meta> tag
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; frame-ancestors 'none'">
The frame-ancestors directive is ignored in <meta> elements and may trigger a validation warning.
✅ Removing the unsupported directive from the <meta> tag
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'">
Deliver frame-ancestors via an HTTP response header on your server instead.
❌ Using commas instead of semicolons between directives
<meta http-equiv="Content-Security-Policy"
content="default-src 'self', script-src 'none', style-src 'self'">
✅ Using semicolons to separate directives
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'none'; style-src 'self'">
❌ Quoting a hostname (hostnames must not be in quotes)
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; img-src 'https://images.example.com'">
✅ Hostname without quotes
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; img-src https://images.example.com">
When in doubt, use an online CSP evaluator to validate your policy string before adding it to your HTML. This ensures both syntactic correctness and that the policy actually enforces what you intend.
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