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 <tt> element was a purely presentational element — it described how text should look (monospaced) rather than what the text meant. HTML5 removed it as part of a broader effort to separate content from presentation. Browsers still render <tt> for backward compatibility, but validators will flag it as obsolete, and its use is discouraged in all new code.
The key issue is that <tt> was used for many different purposes — displaying code snippets, keyboard input, sample output, variables, filenames, and more — yet it conveyed none of that meaning to assistive technologies or search engines. HTML5 provides dedicated semantic elements for most of these use cases, making your content more meaningful and accessible.
Choosing the Right Replacement
Before reaching for a generic <span>, consider what the monospaced text actually represents:
- Code: Use
<code>for inline code fragments (e.g., variable names, short statements). - Keyboard input: Use
<kbd>for text the user should type. - Sample output: Use
<samp>for output from a program or system. - Variables: Use
<var>for mathematical or programming variables. - No semantic meaning: Use a
<span>with CSS when the monospaced styling is purely visual and none of the above elements apply.
All of these elements render in a monospaced font by default in most browsers (except <var>, which is typically italic). You can further style them with CSS as needed.
Examples
❌ Obsolete usage with <tt>
<p>Run the command <tt>npm install</tt> to install dependencies.</p>
<p>The variable <tt>x</tt> holds the result.</p>
<p>The output was <tt>Hello, world!</tt></p>
These all trigger the validator error: The "tt" element is obsolete. Use CSS instead.
✅ Fixed with semantic elements
<p>Run the command <kbd>npm install</kbd> to install dependencies.</p>
<p>The variable <var>x</var> holds the result.</p>
<p>The output was <samp>Hello, world!</samp></p>
Each replacement conveys the meaning of the text. <kbd> tells assistive technologies this is something the user types, <var> marks a variable, and <samp> indicates program output.
✅ Fixed with <code> for inline code
<p>Use the <code>Array.prototype.map()</code> method to transform each element.</p>
✅ Fixed with a <span> and CSS when no semantic element fits
If the monospaced text doesn't represent code, input, output, or a variable — for example, a stylistic choice for a filename or an arbitrary design decision — use a <span> with CSS:
<p>Edit the file <spanclass="mono">config.yaml</span> to change the settings.</p>
.mono{
font-family: monospace;
}
This keeps your HTML valid and your styling in the CSS layer where it belongs.
✅ Block-level code with <pre> and <code>
If you previously used <tt> inside a <pre> block for multi-line code, the standard approach is to combine <pre> with <code>:
<pre><code>function greet(name) {
return "Hello, " + name;
}</code></pre>
Summary
Replace every <tt> element with the semantic HTML element that best describes its content — <code>, <kbd>, <samp>, or <var>. If none of these fit, use a <span> styled with font-family: monospace in CSS. This keeps your markup valid, meaningful, and accessible.
In earlier versions of HTML (HTML 4 and XHTML), the type attribute was required on the <style> element to declare the MIME type of the styling language being used. The value was almost always text/css, as CSS has been the dominant stylesheet language for the web since its inception.
With HTML5, the specification changed. The type attribute on <style> now defaults to text/css, and since no browser supports any other styling language, the attribute serves no practical purpose. The WHATWG HTML Living Standard explicitly notes that the attribute is unnecessary and can be omitted. The W3C validator flags its presence as a warning to encourage cleaner, more modern markup.
Why This Matters
- Cleaner code: Removing unnecessary attributes reduces file size (even if marginally) and improves readability. Every attribute should earn its place in your markup.
- Standards compliance: Modern HTML encourages omitting default values when they add no information. Including
type="text/css"signals outdated coding practices. - Consistency: The same principle applies to
<script>elements, wheretype="text/javascript"is also unnecessary. Keeping your markup consistent by omitting both makes your codebase easier to maintain.
How to Fix It
The fix is straightforward: find every <style> element in your HTML that includes a type attribute and remove it. No other changes are needed — the browser behavior will be identical.
If you're working on a large codebase, a simple search for <style type= across your files will help you find all instances.
Examples
❌ Incorrect: Redundant type attribute
<styletype="text/css">
p{
color: red;
}
</style>
<p>This text will be red.</p>
The type="text/css" attribute is unnecessary and triggers the W3C validator warning.
✅ Correct: type attribute omitted
<style>
p{
color: red;
}
</style>
<p>This text will be red.</p>
Without the type attribute, the browser still interprets the contents as CSS — the behavior is exactly the same.
❌ Incorrect: Other variations that also trigger the warning
The warning is triggered regardless of how the type value is formatted:
<styletype="text/css"media="screen">
body{
font-family: sans-serif;
}
</style>
✅ Correct: Other attributes are fine, just remove type
<stylemedia="screen">
body{
font-family: sans-serif;
}
</style>
Note that other valid attributes like media or nonce should be kept — only the type attribute needs to be removed.
The <script> element's type attribute specifies the MIME type of the script. In earlier HTML versions (HTML 4 and XHTML), the type attribute was required and authors had to explicitly declare type="text/javascript". However, the HTML5 specification changed this — JavaScript is now the default scripting language, so when the type attribute is omitted, browsers automatically treat the script as JavaScript.
Because of this default behavior, including type="text/javascript" (or variations like type="application/javascript") is unnecessary. The W3C HTML Validator raises a warning to encourage cleaner, more concise markup. While this isn't an error that will break your page, removing the redundant attribute keeps your HTML lean and aligned with modern standards.
There are legitimate uses for the type attribute on <script> elements, such as type="module" for ES modules or type="application/ld+json" for structured data. These values change the behavior of the <script> element and should absolutely be kept. The validator only flags the attribute when its value is a JavaScript MIME type, since that's already the default.
Examples
Incorrect — unnecessary type attribute
<scripttype="text/javascript"src="app.js"></script>
<scripttype="text/javascript">
console.log("Hello, world!");
</script>
Correct — type attribute removed
<scriptsrc="app.js"></script>
<script>
console.log("Hello, world!");
</script>
Correct — type attribute used for non-default purposes
The type attribute is still necessary and valid when you're using it for something other than plain JavaScript:
<!-- ES module -->
<scripttype="module"src="app.mjs"></script>
<!-- JSON-LD structured data -->
<scripttype="application/ld+json">
{
"@context":"https://schema.org",
"@type":"Organization",
"name":"Example Inc."
}
</script>
<!-- Import map -->
<scripttype="importmap">
{
"imports":{
"utils":"./utils.js"
}
}
</script>
Quick fix checklist
- Search your HTML files for
type="text/javascript"andtype="application/javascript". - Remove the
typeattribute from those<script>tags entirely. - Leave the
typeattribute on any<script>tags that usetype="module",type="importmap",type="application/ld+json", or other non-JavaScript MIME types.
The type attribute on the <menu> element is obsolete and should be removed.
The <menu> element was originally designed to support different types of menus, including type="context" for context menus and type="toolbar" for toolbars. These features were never widely implemented by browsers and have been removed from the HTML specification.
In the current HTML living standard, the <menu> element is simply a semantic alternative to <ul> for representing a list of interactive items or commands, such as a toolbar of buttons. It no longer accepts a type attribute.
If you need a custom context menu (right-click menu), the recommended approach is to use JavaScript to listen for the contextmenu event and display your own custom menu using standard HTML and CSS.
Invalid Example
<menutype="context"id="my-menu">
<menuitemlabel="Copy"></menuitem>
<menuitemlabel="Paste"></menuitem>
</menu>
Valid Example
Using <menu> as a simple list of commands:
<menu>
<li><button>Copy</button></li>
<li><button>Paste</button></li>
</menu>
If you need a custom context menu, handle it with JavaScript:
<divid="target">Right-click here</div>
<menuid="context-menu"style="display: none;position: absolute;">
<li><button>Copy</button></li>
<li><button>Paste</button></li>
</menu>
<script>
consttarget=document.getElementById("target");
constmenu=document.getElementById("context-menu");
target.addEventListener("contextmenu",(e)=>{
e.preventDefault();
menu.style.display="block";
menu.style.left=e.pageX+"px";
menu.style.top=e.pageY+"px";
});
document.addEventListener("click",()=>{
menu.style.display="none";
});
</script>
Note that the <menuitem> element is also obsolete and no longer part of the HTML specification. Use standard elements like <button> or <a> inside <li> elements instead.
The valign attribute was part of earlier HTML specifications (HTML 4.01 and XHTML 1.0) and accepted values like top, middle, bottom, and baseline to control how content was vertically positioned within a table cell. In HTML5, this attribute is obsolete because the specification separates content structure from presentation. All visual styling should be handled through CSS.
This matters for several reasons. First, using obsolete attributes triggers W3C validation errors, which can indicate broader code quality issues. Second, browsers may eventually drop support for legacy presentational attributes, potentially breaking your layout. Third, CSS provides far more flexibility and maintainability — you can style entire tables or groups of cells with a single rule instead of repeating valign on every <td>.
The valign attribute was also valid on <th>, <tr>, <thead>, <tbody>, and <tfoot> elements, and it is obsolete on all of them. The fix is the same: use the CSS vertical-align property.
To fix this issue, remove the valign attribute from your <td> elements and apply the equivalent CSS vertical-align property. The CSS property accepts the same familiar values: top, middle, bottom, and baseline.
Examples
❌ Incorrect: using the obsolete valign attribute
<table>
<tr>
<tdvalign="top">Top-aligned content</td>
<tdvalign="middle">Middle-aligned content</td>
<tdvalign="bottom">Bottom-aligned content</td>
</tr>
</table>
✅ Fixed: using inline CSS
<table>
<tr>
<tdstyle="vertical-align: top;">Top-aligned content</td>
<tdstyle="vertical-align: middle;">Middle-aligned content</td>
<tdstyle="vertical-align: bottom;">Bottom-aligned content</td>
</tr>
</table>
✅ Fixed: using a class-based approach (recommended)
For better maintainability, define reusable CSS classes instead of repeating inline styles:
<style>
.valign-top{vertical-align: top;}
.valign-middle{vertical-align: middle;}
.valign-bottom{vertical-align: bottom;}
</style>
<table>
<tr>
<tdclass="valign-top">Top-aligned content</td>
<tdclass="valign-middle">Middle-aligned content</td>
<tdclass="valign-bottom">Bottom-aligned content</td>
</tr>
</table>
✅ Fixed: applying a default to all cells in a table
If every cell in a table should share the same vertical alignment, target them with a single CSS rule:
<style>
.data-tabletd,
.data-tableth{
vertical-align: top;
}
</style>
<tableclass="data-table">
<tr>
<th>Name</th>
<th>Description</th>
</tr>
<tr>
<td>Item A</td>
<td>A longer description that may<br>span multiple lines</td>
</tr>
</table>
This approach is the most maintainable — you set the alignment once and it applies consistently to every cell, without needing to modify individual <td> or <th> elements.
Unicode allows certain characters — especially accented letters and other composed characters — to be represented in multiple ways. For example, the letter "é" can be a single precomposed character (U+00E9, NFC form) or a base letter "e" (U+0065) followed by a combining acute accent (U+0301, NFD form). While they may look identical on screen, they are different byte sequences. The HTML specification requires that all attribute values use NFC to ensure consistent behavior across browsers, search engines, and assistive technologies.
This matters for several important reasons:
- String matching and comparison: Browsers and scripts may compare attribute values byte-by-byte. An
idvalue in NFD form won't match a CSS selector or fragment identifier targeting the NFC form, causing broken links and broken styles. - Accessibility: Screen readers and other assistive technologies may process NFC and NFD strings differently, potentially mispronouncing text or failing to match ARIA references.
- Interoperability: Different operating systems produce different normalization forms by default (macOS file systems historically use NFD, for example). Copying text from various sources can introduce non-NFC characters without any visual indication.
- Standards compliance: The WHATWG HTML specification and W3C guidance on normalization explicitly recommend NFC for all HTML content.
The issue most commonly appears when attribute values contain accented characters (like in id, class, alt, title, or value attributes) that were copied from a source using NFD normalization, or when files are created on systems that default to NFD.
To fix the problem, you need to convert the affected attribute values to NFC. You can do this by:
- Retyping the characters directly in your editor, which usually produces NFC by default.
- Using a programming tool such as Python's
unicodedata.normalize('NFC', text), JavaScript'stext.normalize('NFC'), or similar utilities in your language of choice. - Using a text editor that supports normalization conversion (some editors have built-in Unicode normalization features or plugins).
- Running a batch conversion on your HTML files before deployment as part of your build process.
Examples
Incorrect: Attribute value uses NFD (decomposed form)
In this example, the id attribute value for "résumé" uses decomposed characters (base letter + combining accent), which triggers the validation error. The decomposition is invisible in source code but present at the byte level.
<!-- The "é" here is stored as "e" + combining acute accent (NFD) -->
<divid="résumé">
<p>My résumé content</p>
</div>
Correct: Attribute value uses NFC (precomposed form)
Here, the id attribute value uses precomposed characters, which is the correct NFC form.
<!-- The "é" here is stored as a single precomposed character (NFC) -->
<divid="résumé">
<p>My résumé content</p>
</div>
While these two examples look identical in source view, they differ at the byte level. You can verify the normalization form using browser developer tools or a hex editor.
Checking and fixing with JavaScript
You can programmatically normalize attribute values:
<script>
// Check if a string is in NFC
consttext="résumé";
constnfcText=text.normalize("NFC");
console.log(text===nfcText);// false if original was NFD
</script>
Checking and fixing with Python
importunicodedata
text="r\u0065\u0301sume\u0301"# NFD form
normalized=unicodedata.normalize('NFC', text)
print(normalized) # Outputs NFC form: "résumé"
If you encounter this validation error, inspect the flagged attribute value carefully and ensure all characters are in their precomposed NFC form. Adding a normalization step to your build pipeline is a reliable way to prevent this issue from recurring.
The <label> element associates descriptive text with a specific form control, enabling users to click the label to focus or activate the associated input. The for attribute creates this link by referencing the id of the target form control. When the referenced id doesn't correspond to a valid, non-hidden form control, the label becomes orphaned — it isn't associated with anything meaningful.
The W3C validator raises this error in several scenarios:
- The
forattribute references anidthat doesn't exist in the document. - The
forattribute references an element that isn't a labelable element (such as a<div>or<span>). - The
forattribute references an<input type="hidden">, which is not a visible form control and cannot be labeled. - There's a typo or mismatch between the
forvalue and the intended element'sid.
Labelable elements in HTML include <input> (except type="hidden"), <select>, <textarea>, <button>, <meter>, <output>, and <progress>.
This matters for accessibility because screen readers rely on the for/id association to announce what each form control represents. Without a valid association, users who depend on assistive technology may not understand what a form field is asking for. It also impacts usability — a properly linked label expands the clickable area for the form control, making it easier to interact with, especially on touch devices and for users with motor impairments.
To fix this issue, verify that the for attribute value exactly matches the id of a visible, labelable form control. If you're labeling a hidden input, consider whether the label is necessary at all (hidden inputs are not user-facing). If the target element isn't a form control, either change it to the appropriate form element or use a different approach like aria-labelledby.
Examples
❌ for references a non-existent ID
<labelfor="username">Username</label>
<inputid="user-name"type="text">
The for value "username" doesn't match the input's id of "user-name".
✅ Fixed: matching for and id
<labelfor="username">Username</label>
<inputid="username"type="text">
❌ for references a hidden input
<labelfor="token">Token</label>
<inputid="token"type="hidden"value="abc123">
An <input type="hidden"> is not a visible form control and cannot be labeled.
✅ Fixed: remove the unnecessary label
<inputid="token"type="hidden"value="abc123">
Hidden inputs don't need labels since users never interact with them directly.
❌ for references a non-labelable element
<labelfor="info">Information</label>
<divid="info">Some details here</div>
A <div> is not a labelable element, so the for association is invalid.
✅ Fixed: use a proper form control or implicit labeling
<labelfor="info">Information</label>
<textareaid="info"></textarea>
❌ for references an element inside another form control
<labelfor="opt">Choose one</label>
<select>
<optionid="opt"value="a">Option A</option>
</select>
An <option> element is not a labelable element. The label should point to the <select>.
✅ Fixed: reference the <select> element
<labelfor="choice">Choose one</label>
<selectid="choice">
<optionvalue="a">Option A</option>
</select>
Using implicit labeling as an alternative
Instead of using the for attribute, you can wrap the form control inside the <label> element. This creates an implicit association without needing for or id at all:
<label>
Age
<inputtype="number">
</label>
This approach avoids the for/id mismatch problem entirely and is equally valid for accessibility.
In earlier versions of HTML, the version attribute on the <html> element served as a way to indicate the DTD (Document Type Definition) the document followed. For example, you might have seen something like <html version="-//W3C//DTD HTML 4.01//EN">. This was largely redundant even then, because the <!DOCTYPE> declaration at the top of the document already communicated the same information to browsers and validators.
With the introduction of HTML5, the version attribute was officially marked as obsolete. The HTML Living Standard maintained by WHATWG does not define or support it. Modern browsers completely ignore it, so it has no functional effect on rendering or behavior. However, keeping it in your markup produces a validation warning from the W3C HTML Validator and adds unnecessary clutter to your code.
Removing this attribute has no side effects. The <!DOCTYPE html> declaration is the only mechanism needed to signal that your document uses the current HTML standard. Keeping your markup clean and free of obsolete attributes improves maintainability and ensures your documents pass validation without unnecessary warnings.
How to fix it
- Locate the
<html>tag in your document. - Remove the
versionattribute and its value entirely. - Ensure you have a valid
<!DOCTYPE html>declaration at the top of the document. - Keep the
langattribute on the<html>element, as it is important for accessibility and internationalization.
Examples
Incorrect: using the obsolete version attribute
<!DOCTYPE html>
<htmlversion="-//W3C//DTD HTML 4.01//EN"lang="en">
<head>
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
This triggers the W3C validator warning: The "version" attribute on the "html" element is obsolete.
Correct: version attribute removed
<!DOCTYPE html>
<htmllang="en">
<head>
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
The version attribute has been removed, and the document remains fully valid. The <!DOCTYPE html> declaration and the lang attribute are the only things needed on the <html> element for a well-formed, standards-compliant document.
In earlier versions of HTML (HTML 4 and XHTML 1.0), the width attribute was a standard way to control the dimensions of tables and their cells. HTML5 marked this attribute as obsolete on table-related elements, meaning browsers may still render it, but it is no longer conforming HTML. The W3C validator will report a warning or error whenever it encounters this usage.
This matters for several reasons. First, using obsolete attributes means your markup doesn't conform to the HTML living standard, which can cause validation failures that obscure more critical issues. Second, relying on presentational HTML attributes mixes content with presentation, making your code harder to maintain. CSS provides far more flexibility and control — you can use relative units, media queries for responsive layouts, and centralized stylesheets that apply consistently across your site. Third, while current browsers still support the obsolete width attribute, future browser versions are not guaranteed to do so.
The fix is straightforward: remove the width attribute and apply the equivalent sizing with CSS. You can use inline styles for quick fixes, but a class-based or external stylesheet approach is generally preferred for maintainability.
Examples
❌ Incorrect: using the obsolete width attribute
<tablewidth="600">
<tr>
<tdwidth="200">Name</td>
<tdwidth="400">Description</td>
</tr>
</table>
This triggers the validator message: The "width" attribute on the "table" element is obsolete. Use CSS instead. — and the same for each <td>.
✅ Fixed: using inline CSS
<tablestyle="width:600px;">
<tr>
<tdstyle="width:200px;">Name</td>
<tdstyle="width:400px;">Description</td>
</tr>
</table>
✅ Better: using CSS classes
<style>
.data-table{
width:100%;
max-width:600px;
}
.data-table.col-name{
width:33%;
}
.data-table.col-desc{
width:67%;
}
</style>
<tableclass="data-table">
<tr>
<tdclass="col-name">Name</td>
<tdclass="col-desc">Description</td>
</tr>
</table>
Using classes keeps your HTML clean and makes it easy to adjust sizing in one place. Percentage-based widths also adapt better to different screen sizes.
❌ Incorrect: width on <col> and <colgroup>
<table>
<colgroupwidth="100">
<colwidth="50">
<colwidth="50">
</colgroup>
<tr>
<td>A</td>
<td>B</td>
</tr>
</table>
✅ Fixed: CSS on <col> elements
<table>
<colgroup>
<colstyle="width:50px;">
<colstyle="width:50px;">
</colgroup>
<tr>
<td>A</td>
<td>B</td>
</tr>
</table>
❌ Incorrect: width on <th>
<table>
<tr>
<thwidth="150">Header</th>
</tr>
</table>
✅ Fixed
<table>
<tr>
<thstyle="width:150px;">Header</th>
</tr>
</table>
Tips for migrating
- Search your codebase for
width=inside table-related tags. A regex like<(table|td|th|col|colgroup)[^>]+width=can help locate all instances. - Convert pixel values directly — a
width="200"attribute is equivalent towidth: 200pxin CSS. - Consider responsive design — this is a good opportunity to switch from fixed pixel widths to percentages,
emunits, or other flexible values. - Use
table-layout: fixedon the<table>element if you need columns to respect exact widths rather than auto-sizing based on content.
In earlier versions of HTML (HTML4 and XHTML), it was common to use attributes like align="center", bgcolor="#ff0000", or border="1" directly on elements to control their appearance. HTML5 enforces a clear separation between structure (HTML) and presentation (CSS). These presentational attributes are now considered obsolete, and the HTML specification instructs authors to use CSS for all visual styling.
This matters for several reasons:
- Standards compliance: Using obsolete attributes produces invalid HTML5, which can lead to unpredictable rendering across browsers.
- Maintainability: Scattering styling across HTML attributes makes it harder to update your site's design. CSS allows you to manage styles from a single location.
- Accessibility: Screen readers and assistive technologies work best with well-structured, standards-compliant HTML. Mixing presentation into markup can confuse these tools.
- Future-proofing: Browsers may eventually drop support for obsolete attributes entirely, breaking your layout.
To fix this issue, identify the obsolete attribute in your HTML, determine the equivalent CSS property, remove the attribute, and apply the CSS rule using a style attribute, an internal <style> block, or an external stylesheet.
Here is a reference of common obsolete attributes and their CSS equivalents:
| Obsolete Attribute | CSS Equivalent |
|---|---|
align="center" | text-align: center or margin: auto |
bgcolor="#fff" | background-color: #fff |
border="1" | border: 1px solid |
cellpadding="10" | padding: 10px (on td/th) |
cellspacing="5" | border-spacing: 5px (on table) |
width="500" | width: 500px |
height="300" | height: 300px |
valign="top" | vertical-align: top |
Examples
Obsolete align on a paragraph
❌ Incorrect: Using the obsolete align attribute on a <p> element.
<palign="center">Welcome to my site.</p>
✅ Fixed: Use the CSS text-align property instead.
<pstyle="text-align: center;">Welcome to my site.</p>
Obsolete bgcolor on a table
❌ Incorrect: Using bgcolor on a <table> element.
<tablebgcolor="#f0f0f0">
<tr>
<td>Data</td>
</tr>
</table>
✅ Fixed: Use background-color in CSS.
<tablestyle="background-color:#f0f0f0;">
<tr>
<td>Data</td>
</tr>
</table>
Obsolete cellpadding and cellspacing on a table
❌ Incorrect: Using cellpadding and cellspacing attributes.
<tablecellpadding="10"cellspacing="5">
<tr>
<td>Item</td>
<td>Price</td>
</tr>
</table>
✅ Fixed: Use border-spacing on the table and padding on the cells.
<tablestyle="border-spacing:5px;">
<tr>
<tdstyle="padding:10px;">Item</td>
<tdstyle="padding:10px;">Price</td>
</tr>
</table>
Obsolete width on an image
❌ Incorrect (in some contexts): Note that width and height on <img> elements are actually not obsolete — they are valid in HTML5 and recommended for layout stability. However, these attributes are obsolete on elements like <table>, <td>, <th>, and <hr>.
<tablewidth="600">
<tr>
<td>Content</td>
</tr>
</table>
✅ Fixed: Apply width via CSS.
<tablestyle="width:600px;">
<tr>
<td>Content</td>
</tr>
</table>
Using an external stylesheet (preferred approach)
For maintainability, move styles out of inline style attributes and into a stylesheet.
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>My Page</title>
<style>
.data-table{
width:600px;
background-color:#f0f0f0;
border-spacing:5px;
}
.data-tabletd{
padding:10px;
}
</style>
</head>
<body>
<tableclass="data-table">
<tr>
<td>Item</td>
<td>Price</td>
</tr>
</table>
</body>
</html>
This approach keeps your HTML clean and semantic while giving you full control over presentation through CSS.
The dir attribute specifies the base text direction for the content of an element. When a document is written in a right-to-left language like Arabic, Hebrew, Persian (Farsi), or Urdu, the browser needs to know that text should flow from right to left. Without this attribute, browsers default to left-to-right (ltr) rendering, which can cause a range of problems: text alignment may be incorrect, punctuation can appear in the wrong place, and the overall page layout may look broken or confusing to native readers.
This matters for several important reasons:
- Accessibility: Screen readers and other assistive technologies rely on the
dirattribute to correctly announce and navigate content. Without it, the reading experience for users relying on these tools may be disorienting or incorrect. - Visual correctness: Elements like lists, tables, form labels, and navigation menus will mirror their layout in RTL mode. Without
dir="rtl", these elements default to LTR positioning, which feels unnatural for RTL language speakers. - Bidirectional text handling: Documents often contain mixed-direction content (e.g., Arabic text with embedded English words, numbers, or brand names). The Unicode Bidirectional Algorithm (BiDi) uses the base direction set by
dirto correctly resolve the ordering of these mixed runs of text. - Standards compliance: The WHATWG HTML Living Standard recommends that authors set the
dirattribute to match the language of the document, and the W3C Internationalization guidelines strongly encourage it for RTL languages.
How to fix it
Add dir="rtl" to your <html> start tag. If you haven't already, also include the appropriate lang attribute for the specific language you're using.
For Arabic:
<htmldir="rtl"lang="ar">
For Hebrew:
<htmldir="rtl"lang="he">
For Persian:
<htmldir="rtl"lang="fa">
Examples
❌ Missing dir attribute on an Arabic document
<!DOCTYPE html>
<htmllang="ar">
<head>
<metacharset="utf-8">
<title>مرحبا بالعالم</title>
</head>
<body>
<h1>مرحبا بالعالم</h1>
<p>هذه صفحة تجريبية باللغة العربية.</p>
</body>
</html>
The browser will render this page with a left-to-right base direction. The heading and paragraph text may appear left-aligned, and any mixed-direction content (like embedded numbers or English words) may be ordered incorrectly.
✅ Correct: dir="rtl" added to the <html> tag
<!DOCTYPE html>
<htmldir="rtl"lang="ar">
<head>
<metacharset="utf-8">
<title>مرحبا بالعالم</title>
</head>
<body>
<h1>مرحبا بالعالم</h1>
<p>هذه صفحة تجريبية باللغة العربية.</p>
</body>
</html>
Now the browser knows to render the page right-to-left. Text will be right-aligned by default, scroll bars will appear on the left, and the overall layout will feel natural for Arabic readers.
Handling mixed-direction content within a page
If your RTL document contains sections in a left-to-right language, use the dir attribute on individual elements to override the base direction locally:
<p>قام المستخدم بزيارة <spandir="ltr">www.example.com</span> اليوم.</p>
This ensures the URL renders in the correct left-to-right order while the surrounding Arabic text flows right-to-left. Setting dir="rtl" on the <html> element establishes the correct default, and you only need per-element overrides for embedded LTR content.
The lang attribute on the <html> element declares the primary language of the document's content. When this attribute is left empty (lang=""), it effectively tells browsers and assistive technologies that the language is unknown or intentionally unspecified — which is almost never what you want.
This matters for several important reasons:
- Accessibility: Screen readers rely on the
langattribute to select the correct pronunciation engine. An empty value can cause a screen reader to fall back to a default language, potentially reading English text with incorrect pronunciation rules. - Search engines: Search engines use the
langattribute to understand what language your content is in, which helps serve your pages to the right audience. - Browser features: Browsers use the language declaration for hyphenation, spell-checking, font selection, and other language-sensitive rendering decisions.
- Standards compliance: The WHATWG HTML living standard specifies that if the
langattribute is present, its value must be a valid BCP 47 language tag. An empty string is not a valid language tag.
The fix is straightforward: set the lang attribute to a valid BCP 47 language tag that matches your content. For English, common values include en (general English), en-US (American English), or en-GB (British English). If your content is in another language, use the appropriate tag (e.g., fr for French, de for German, ja for Japanese).
Examples
❌ Empty lang attribute (triggers the warning)
<!DOCTYPE html>
<htmllang="">
<head>
<title>My Page</title>
</head>
<body>
<h1>Welcome to my website</h1>
</body>
</html>
❌ Missing lang attribute entirely
While a missing lang attribute triggers a different warning, it causes the same underlying problem — no language is declared:
<!DOCTYPE html>
<html>
<head>
<title>My Page</title>
</head>
<body>
<h1>Welcome to my website</h1>
</body>
</html>
✅ Correct: specifying the language
<!DOCTYPE html>
<htmllang="en">
<head>
<title>My Page</title>
</head>
<body>
<h1>Welcome to my website</h1>
</body>
</html>
✅ Correct: using a regional variant
<!DOCTYPE html>
<htmllang="en-US">
<head>
<title>My Page</title>
</head>
<body>
<h1>Welcome to my website</h1>
</body>
</html>
Using lang for mixed-language content
If your document is primarily in English but contains sections in other languages, set lang="en" on the <html> element and override it on specific elements:
<!DOCTYPE html>
<htmllang="en">
<head>
<title>Multilingual Page</title>
</head>
<body>
<h1>Welcome</h1>
<p>This page contains a quote in French:</p>
<blockquotelang="fr">
<p>La vie est belle.</p>
</blockquote>
</body>
</html>
The lang attribute on the <html> element declares the primary language of the document. The W3C validator uses heuristics to analyze the text content of your page, and when it detects a mismatch between the declared language and the apparent language, it raises this warning. For example, if your page content is written in English but lang="fr" (French) is set, the validator will flag this inconsistency.
Why This Matters
The lang attribute plays a critical role in several areas:
- Accessibility: Screen readers use the
langattribute to select the correct pronunciation rules and voice profile. If a page is written in English but declares French, a screen reader may attempt to read the content with French pronunciation, making it unintelligible to the user. - Search engines: Search engines use the
langattribute to understand what language a page is in, which affects how the page is indexed and served in search results for different regions. - Browser features: Browsers rely on the
langattribute for built-in translation prompts, spell-checking, hyphenation, and font selection. An incorrect value can cause unexpected behavior in all of these areas.
How to Fix It
- Identify the primary language of your content. Look at the actual text on your page — what language is the majority of it written in?
- Update the
langattribute to the correct BCP 47 language tag for that language (e.g.,enfor English,frfor French,esfor Spanish). - If the
langattribute is already correct and the validator's heuristic is wrong (e.g., your page genuinely is in another language but contains some English text or code), you can safely ignore this warning.
For pages with mixed-language content, set the lang attribute on <html> to the primary language, then use lang attributes on specific elements to mark sections in other languages.
Examples
❌ Incorrect: Content is in English but lang declares French
<!DOCTYPE html>
<htmllang="fr">
<head>
<title>My Website</title>
</head>
<body>
<h1>Welcome to my website</h1>
<p>This is an English paragraph about web development.</p>
</body>
</html>
The validator detects that the content is English, but lang="fr" says it's French.
✅ Fixed: lang attribute matches the content language
<!DOCTYPE html>
<htmllang="en">
<head>
<title>My Website</title>
</head>
<body>
<h1>Welcome to my website</h1>
<p>This is an English paragraph about web development.</p>
</body>
</html>
✅ Mixed-language content with proper lang attributes
If your page is primarily in English but contains sections in another language, set the document language to en and annotate the foreign-language sections individually:
<!DOCTYPE html>
<htmllang="en">
<head>
<title>Language Examples</title>
</head>
<body>
<h1>Welcome</h1>
<p>This page is mostly in English.</p>
<blockquotelang="fr">
<p>Ceci est une citation en français.</p>
</blockquote>
</body>
</html>
This approach ensures screen readers switch pronunciation only for the French <blockquote>, while the rest of the page is correctly read as English.
The lang attribute on the <html> element tells browsers, search engines, and assistive technologies what language the page content is written in. The validator uses heuristic analysis of the actual text on the page to detect the likely language, and when there's a mismatch, it flags the discrepancy.
Why This Matters
An incorrect lang attribute causes real problems for users and systems that rely on it:
- Screen readers use the
langattribute to select the correct pronunciation engine. A French document marked as English will be read aloud with English pronunciation rules, making it incomprehensible. - Search engines use the language declaration for indexing and serving results to users searching in a specific language.
- Browser features like automatic translation prompts and spell-checking rely on the declared language.
- Hyphenation and typographic rules in CSS also depend on the correct language being declared.
Common Causes
- Copy-pasting a boilerplate — Starting from an English template but writing content in another language without updating
lang. - Multilingual sites — Using the same base template for all language versions without dynamically setting the
langvalue. - Incorrect language subtag — Using the wrong BCP 47 language tag (e.g.,
lang="en"instead oflang="de"for German content).
When You Can Safely Ignore This Warning
This is a warning, not an error. The validator's language detection is heuristic and not always accurate. You may safely ignore it if:
- Your page contains very little text, making detection unreliable.
- The page has significant amounts of content in multiple languages, but the
langattribute correctly reflects the primary language. - The detected language is simply wrong (e.g., short text snippets can confuse the detector).
If you're confident the lang attribute is correct, you can disregard the warning.
How to Fix It
Identify the primary language of your document's content and set the lang attribute to the appropriate BCP 47 language tag. Common tags include en (English), fr (French), de (German), es (Spanish), pt (Portuguese), ja (Japanese), and zh (Chinese).
Examples
Incorrect: Content in French, but lang set to English
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>Mon site</title>
</head>
<body>
<h1>Bienvenue sur notre site</h1>
<p>Nous sommes ravis de vous accueillir sur notre plateforme.</p>
</body>
</html>
This triggers the warning because the validator detects French content but sees lang="en".
Fixed: lang attribute matches the content language
<!DOCTYPE html>
<htmllang="fr">
<head>
<metacharset="utf-8">
<title>Mon site</title>
</head>
<body>
<h1>Bienvenue sur notre site</h1>
<p>Nous sommes ravis de vous accueillir sur notre plateforme.</p>
</body>
</html>
Handling mixed-language content
If your page is primarily in one language but contains sections in another, set the lang attribute on the <html> element to the primary language and use lang on specific elements for the other language:
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>Our Global Site</title>
</head>
<body>
<h1>Welcome to our site</h1>
<p>We are glad you are here.</p>
<blockquotelang="fr">
<p>La vie est belle.</p>
</blockquote>
</body>
</html>
This tells assistive technologies that the page is in English, but the blockquote should be read using French pronunciation rules. The validator should not flag this as a mismatch because the majority of the content is in English.
The lang attribute on the <html> element declares the primary language of the document's content. When this attribute is missing or set incorrectly, the validator analyzes the text content and attempts to detect the language automatically. If it identifies a likely language, it produces this warning suggesting you add the appropriate lang value.
This matters for several important reasons:
- Accessibility: Screen readers rely on the
langattribute to select the correct pronunciation rules and voice profile. Without it, a screen reader might attempt to read Spanish text using English phonetics, producing unintelligible speech for the user. - Browser behavior: Browsers use the language declaration for hyphenation, quotation mark styling, spell-checking, and font selection. For example, the CSS
hyphens: autoproperty depends on thelangattribute to apply language-appropriate hyphenation rules. - Search engines: Search engines use the
langattribute as a signal to understand the language of your content, which helps serve it to the right audience in search results. - Translation tools: Automatic translation services use the declared language to determine whether (and from which language) to offer translation.
The value of the lang attribute must be a valid BCP 47 language tag. Common examples include en (English), es (Spanish), fr (French), de (German), zh (Chinese), ja (Japanese), and ar (Arabic). You can also specify regional variants like en-US, en-GB, pt-BR, or es-MX.
Examples
Missing lang attribute
This triggers the warning because the validator detects the content language but finds no lang declaration:
<!DOCTYPE html>
<html>
<head>
<metacharset="UTF-8">
<title>Mi Sitio Web</title>
</head>
<body>
<h1>Bienvenido a mi sitio web</h1>
<p>Este es un párrafo en español.</p>
</body>
</html>
Fixed with the correct lang attribute
Adding lang="es" tells browsers, screen readers, and search engines that this document is in Spanish:
<!DOCTYPE html>
<htmllang="es">
<head>
<metacharset="UTF-8">
<title>Mi Sitio Web</title>
</head>
<body>
<h1>Bienvenido a mi sitio web</h1>
<p>Este es un párrafo en español.</p>
</body>
</html>
Mismatched lang attribute
This can also trigger the warning when the lang value doesn't match the actual content. Here, the content is in French but the language is declared as English:
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="UTF-8">
<title>Mon Site Web</title>
</head>
<body>
<h1>Bienvenue sur mon site web</h1>
<p>Ceci est un paragraphe en français.</p>
</body>
</html>
The fix is to correct the lang value to match the content:
<!DOCTYPE html>
<htmllang="fr">
<head>
<metacharset="UTF-8">
<title>Mon Site Web</title>
</head>
<body>
<h1>Bienvenue sur mon site web</h1>
<p>Ceci est un paragraphe en français.</p>
</body>
</html>
Using a regional variant
If you need to specify a regional variation, append the region subtag. For example, es-MX for Mexican Spanish or pt-BR for Brazilian Portuguese:
<!DOCTYPE html>
<htmllang="es-MX">
<head>
<metacharset="UTF-8">
<title>Mi Sitio Web</title>
</head>
<body>
<h1>Bienvenido a mi sitio web</h1>
<p>Este es un párrafo en español de México.</p>
</body>
</html>
Sections in a different language
When your document is primarily in one language but contains sections in another, set the main language on <html> and use lang on individual elements for the exceptions:
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="UTF-8">
<title>My Website</title>
</head>
<body>
<h1>Welcome to my website</h1>
<p>This site is available in multiple languages.</p>
<blockquotelang="es">
<p>Bienvenido a mi sitio web.</p>
</blockquote>
</body>
</html>
This approach ensures that assistive technologies switch pronunciation rules appropriately when they encounter the foreign-language section, while browsers and search engines still understand the primary document language.
A document lacks a heading with a computed level of 1, which means there is no <h1> element (or an element whose final heading level resolves to 1) on the page.
The W3C validator raises this as a warning because every document should have at least one <h1> to identify its main topic. Screen readers and search engines rely on the heading hierarchy to understand page structure, and <h1> is the expected entry point of that hierarchy.
A "computed heading level" differs from the literal tag name only when the now-obsolete <hgroup> algorithm or the abandoned outline algorithm would adjust levels. In practice, for nearly all documents, a computed level of 1 simply means an <h1> tag.
This warning appears when a page uses <h2>, <h3>, or other headings but skips <h1> entirely. Adding a single <h1> that describes the page content fixes the warning.
Example with the warning
<!DOCTYPE html>
<htmllang="en">
<head>
<title>My page</title>
</head>
<body>
<h2>About us</h2>
<p>Welcome to our site.</p>
<h3>Our team</h3>
<p>We are a small group.</p>
</body>
</html>
Fixed example
<!DOCTYPE html>
<htmllang="en">
<head>
<title>My page</title>
</head>
<body>
<h1>My page</h1>
<h2>About us</h2>
<p>Welcome to our site.</p>
<h3>Our team</h3>
<p>We are a small group.</p>
</body>
</html>
Each page should have exactly one <h1>. If the design calls for the <h1> to be visually hidden (for example, when a logo replaces the text), use a CSS technique to hide it from sighted users while keeping it accessible to screen readers, rather than removing it from the markup.
Void elements — such as area, base, br, col, embed, hr, img, input, link, meta, source, track, and wbr — cannot have child content or a closing tag. They are inherently self-closing. In XHTML, a trailing slash (<br />) was required to make the document well-formed XML, but in HTML5 (the current standard), the slash is entirely optional and has no effect on how the element is parsed.
While the trailing slash is technically permitted, it creates a real risk when used alongside unquoted attribute values. The HTML parser does not treat the slash as a special self-closing token in the way you might expect. Instead, if the last attribute's value is unquoted, the slash becomes part of that value. This can silently break your page — the browser won't report an error, but the attribute value will be wrong.
Beyond this parsing hazard, removing trailing slashes keeps your markup cleaner and avoids ambiguity. The W3C validator raises this as a warning to help you catch potential issues before they cause hard-to-debug problems.
Why this matters
- Silent bugs: When a trailing slash is absorbed into an unquoted attribute value, it changes the value without any visible error. For example, a URL ending in
/instead of the intended value can cause broken images, failed resource loads, or incorrect behavior. - Standards compliance: The WHATWG HTML Living Standard specifies that the trailing slash on void elements is allowed but has no effect. The validator warns about it because of the interaction with unquoted attributes.
- Code clarity: Removing trailing slashes makes it clear that you're writing HTML, not XHTML, and avoids confusing developers who may not be aware of the parsing nuance.
How to fix it
- Remove trailing slashes from all void elements. Write
<br>instead of<br/>,<img src="photo.jpg" alt="Photo">instead of<img src="photo.jpg" alt="Photo"/>, and so on. - If you prefer to keep trailing slashes, always quote your attribute values. Quoted values prevent the parser from absorbing the slash into the attribute.
- Configure your editor or formatter — some tools (e.g., Prettier) have options to add or remove trailing slashes. Check your formatter's settings and disable automatic trailing slash insertion for HTML files.
Examples
Trailing slash absorbed into an unquoted attribute value
In this example, the parser interprets the trailing slash as part of the src value, resulting in http://example.com/logo.svg/ instead of the intended http://example.com/logo.svg:
<!-- ❌ Wrong: the slash becomes part of the src value -->
<imgalt=SVGsrc=http://example.com/logo.svg/>
Fix by removing the trailing slash
The simplest and recommended fix — just drop the slash:
<!-- ✅ Correct: no trailing slash -->
<imgalt=SVGsrc=http://example.com/logo.svg>
Fix by quoting attribute values
If you want to keep the trailing slash for stylistic reasons, quoting the attribute values prevents the slash from being absorbed:
<!-- ✅ Correct: quoted attributes prevent the parsing issue -->
<imgalt="SVG"src="http://example.com/logo.svg"/>
Common void elements with unnecessary trailing slashes
<!-- ❌ Trailing slashes have no effect -->
<br/>
<hr/>
<inputtype="text"name="query"/>
<metacharset="utf-8"/>
<linkrel="stylesheet"href="style.css"/>
<!-- ✅ Preferred: clean void elements without trailing slashes -->
<br>
<hr>
<inputtype="text"name="query">
<metacharset="utf-8">
<linkrel="stylesheet"href="style.css">
A subtle real-world bug
Consider a meta tag with an unquoted content value:
<!-- ❌ The slash is parsed as part of the content value -->
<metaname=theme-colorcontent=#ffffff/>
The browser reads the content value as #ffffff/ instead of #ffffff, which is not a valid color and will be ignored. The fix:
<!-- ✅ No trailing slash -->
<metaname="theme-color"content="#ffffff">
The <span> element is an inline container used to mark up a portion of text or a group of inline elements, typically for styling or scripting purposes. Like most HTML elements, it requires both an opening <span> tag and a closing </span> tag. When the closing tag is missing, the browser's HTML parser must determine where the element ends on its own. Different browsers may handle this differently, potentially wrapping more content inside the <span> than intended.
This is problematic for several reasons:
- Unexpected styling: If the
<span>has CSS applied to it (via aclass,id, or inlinestyle), the styles may bleed into sibling or subsequent elements that were never meant to be affected. - Broken document structure: An unclosed
<span>inside a<p>element can cause the parser to implicitly close and reopen elements in unexpected ways, distorting the DOM tree. - Accessibility concerns: Screen readers and assistive technologies rely on a well-formed DOM. An unclosed element can cause content to be grouped or announced incorrectly.
- Maintenance difficulty: Unclosed elements make the markup harder to read, debug, and maintain over time.
A common scenario is forgetting the closing tag when the <span> wraps only part of a paragraph's text. Another frequent cause is mismatched or misordered nesting, where the closing tags appear in the wrong sequence.
Examples
Unclosed <span> inside a paragraph
The closing </span> is missing entirely, so the browser has to guess where the span ends:
<!-- ❌ Bad: unclosed <span> -->
<p><spanclass="highlight">I'm forgetting something</p>
<p>Life goes on</p>
Add the missing </span> before the content that shouldn't be wrapped:
<!-- ✅ Good: <span> is properly closed -->
<p><spanclass="highlight">I'm forgetting something</span></p>
<p>Life goes on</p>
Incorrectly nested closing tags
Sometimes the closing tags are present but in the wrong order. Tags must be closed in the reverse order they were opened:
<!-- ❌ Bad: closing tags are misordered -->
<p><em><spanclass="note">Important text</em></span></p>
<!-- ✅ Good: tags closed in correct (reverse) order -->
<p><em><spanclass="note">Important text</span></em></p>
Multiple <span> elements with a missing close
When using several <span> elements in sequence, it's easy to lose track:
<!-- ❌ Bad: second <span> is never closed -->
<p>
<spanclass="first-name">Jane</span>
<spanclass="last-name">Doe
</p>
<!-- ✅ Good: all <span> elements are closed -->
<p>
<spanclass="first-name">Jane</span>
<spanclass="last-name">Doe</span>
</p>
Tips for avoiding unclosed elements
- Indent consistently. Proper indentation makes it much easier to spot a missing closing tag.
- Write both tags at once. When you type
<span>, immediately type</span>and then fill in the content between them. - Use an editor with auto-closing and bracket matching. Most modern code editors will highlight unmatched tags.
- Validate regularly. Run your markup through the W3C HTML Validator often, especially after larger edits.
Every non-void HTML element needs a matching end tag. When one is missing, the browser's error recovery kicks in and decides where the element ends. The resulting DOM tree can differ from what the source code suggests, breaking layout, CSS selectors, and JavaScript that depends on a specific structure. Screen readers and other assistive technologies also rely on correct nesting to convey document structure to users.
This error commonly appears when tags are closed out of order. HTML requires last-in-first-out nesting: the most recently opened element must be closed first. Another frequent cause is copy-paste editing that removes a closing tag while leaving the opener behind. Note that void elements (<br>, <img>, <input>, <meta>, <hr>, <link>) never have closing tags. Writing <br></br> is invalid in HTML and can trigger a related error.
Examples
Missing closing tag
<!-- Bad: the outer <div> is never closed -->
<divclass="card">
<h2>Title</h2>
<p>Some text</p>
<divclass="footer">
<p>Footer text</p>
</div>
Fixed
<divclass="card">
<h2>Title</h2>
<p>Some text</p>
<divclass="footer">
<p>Footer text</p>
</div>
</div>
Out-of-order closing tags
<!-- Bad: <em> is opened inside <strong> but closed after </strong> -->
<p><strong><em>Important</strong></em> notice</p>
Fixed
<p><strong><em>Important</em></strong> notice</p>
Consistent indentation makes nesting mismatches easier to spot by eye. Editor features like bracket matching, and tools like Prettier or HTMLHint, can catch these problems before you run the validator. If you use a templating language, make sure conditional blocks do not skip required end tags.
In older HTML practices, developers sometimes used <meta http-equiv="Content-Language" content="en"> inside the <head> to declare the document's primary language. The HTML living standard now marks this as obsolete because it was an unreliable and indirect way to communicate language information. It attempted to mirror an HTTP header rather than being a true part of the document structure, and its behavior was inconsistently implemented across browsers.
The lang attribute on the <html> element is the correct modern approach. It directly associates the language with the document's DOM tree, which has several important benefits:
- Accessibility: Screen readers rely on the
langattribute to select the correct pronunciation rules and voice profile. Without it, assistive technology may mispronounce content or fall back to a default language that doesn't match the text. - Browser behavior: Browsers use the
langattribute to make decisions about hyphenation, font selection, quotation mark styling, spell-checking, and other language-sensitive rendering. - Search engines: Declaring the language helps search engines index and serve content to the appropriate audience.
- CSS targeting: The
:lang()pseudo-class selector works based on thelangattribute, enabling language-specific styling.
The lang attribute also supports granular, element-level language declarations. If your page is primarily in English but contains a French quote, you can set lang="fr" on that specific element — something the <meta> approach could never do.
Examples
Incorrect: using the obsolete meta tag
This triggers the W3C validation warning because <meta http-equiv="Content-Language"> is obsolete for specifying the document language.
<!DOCTYPE html>
<html>
<head>
<metahttp-equiv="Content-Language"content="en">
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
Correct: using the lang attribute on <html>
Remove the <meta http-equiv="Content-Language"> tag and add the lang attribute to the <html> element instead.
<!DOCTYPE html>
<htmllang="en">
<head>
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
Correct: mixed-language content
Use the lang attribute on the root element for the primary language, then override it on specific elements as needed.
<!DOCTYPE html>
<htmllang="en">
<head>
<title>Multilingual Page</title>
</head>
<body>
<p>The French word for hello is <spanlang="fr">bonjour</span>.</p>
<blockquotelang="de">
<p>Die Grenzen meiner Sprache bedeuten die Grenzen meiner Welt.</p>
</blockquote>
</body>
</html>
Common language codes
Use a valid BCP 47 language tag as the value of the lang attribute. Here are some frequently used codes:
| Code | Language |
|---|---|
en | English |
fr | French |
de | German |
es | Spanish |
pt-BR | Brazilian Portuguese |
zh-Hans | Simplified Chinese |
ja | Japanese |
The fix is straightforward: remove any <meta http-equiv="Content-Language"> tags from your <head> and ensure your <html> element includes a lang attribute with the appropriate language code. This single change resolves the validation warning while improving your document's accessibility, rendering, and standards compliance.
The encodings iso-8859-1 (also known as Latin-1) and windows-1252 (also called CP-1252) share the same character mappings for most byte values, but they diverge in the range 0x80 to 0x9F. In iso-8859-1, these bytes map to obscure control characters. In windows-1252, they map to useful printable characters like curly quotes (" "), em dashes (—), the euro sign (€), and the trademark symbol (™). Because Windows text editors historically saved files in windows-1252, many documents declared as iso-8859-1 actually contain windows-1252-specific characters in that byte range.
When the validator encounters bytes in the 0x80–0x9F range in a file declared as iso-8859-1, it knows the file is actually windows-1252, because those bytes would be meaningless control characters under true iso-8859-1. This triggers the warning.
Modern browsers already handle this situation by treating any iso-8859-1 declaration as windows-1252 — the WHATWG Encoding Standard explicitly maps the label iso-8859-1 to the windows-1252 encoding. So in practice, browsers won't break. However, the mismatch still matters for standards compliance, and it signals that you may not have full control over your document's encoding, which can cause subtle bugs in other tools, server configurations, or data processing pipelines.
Why This Matters
- Standards compliance: Declaring one encoding while using another violates the HTML specification's requirement that the declared encoding match the actual encoding of the document.
- Interoperability: While browsers handle this gracefully, other HTML consumers — such as search engine crawlers, screen readers, email clients, or server-side parsers — may not apply the same
iso-8859-1-to-windows-1252mapping, leading to garbled characters. - Maintainability: An encoding mismatch is a sign of a fragile build process. If you ever need to transcode, concatenate, or process your files programmatically, an incorrect declaration can cause data corruption.
How to Fix It
You have three options, listed from most to least recommended:
Option 1: Convert to UTF-8 (Recommended)
UTF-8 is the universal standard for the web. It supports every Unicode character, is the default encoding in the WHATWG HTML Standard, and eliminates this entire class of problems.
- Open your file in your text editor or IDE.
- Re-save it with UTF-8 encoding (most modern editors default to this).
- Declare
utf-8in your<meta>tag. - Verify that special characters (curly quotes, em dashes, etc.) still display correctly after conversion.
Option 2: Declare windows-1252
If you can't convert to UTF-8, update your declaration to match the actual encoding:
- Keep the file saved as
windows-1252. - Change your
<meta>tag to declarewindows-1252.
Option 3: Actually save as iso-8859-1
If you truly need iso-8859-1, you must ensure the file contains no bytes in the 0x80–0x9F range. This means removing or replacing characters like curly quotes, em dashes, and the euro sign, since they don't exist in iso-8859-1.
Examples
Triggering the warning
This document declares iso-8859-1, but if the file is saved in windows-1252 and contains characters like curly quotes or em dashes in the 0x80–0x9F byte range, the validator will report the mismatch:
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="iso-8859-1">
<title>My Page</title>
</head>
<body>
<!-- If this file contains windows-1252 bytes like curly quotes or em dashes, the validator will warn about the encoding mismatch -->
the validator will warn about the encoding mismatch -->
<p>She said, “Hello!”</p>
</body>
</html>
Fix: Convert to UTF-8
The best solution is to save the file as UTF-8 and declare it accordingly:
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>My Page</title>
</head>
<body>
<p>She said, "Hello!"</p>
</body>
</html>
In UTF-8, curly quotes, em dashes, euro signs, and every other Unicode character are natively supported — no more byte-range conflicts.
Fix: Declare windows-1252 explicitly
If converting to UTF-8 isn't feasible, make the declaration honest:
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="windows-1252">
<title>My Page</title>
</head>
<body>
<p>Price: 50€ — bargain!</p>
</body>
</html>
This eliminates the warning because the declared encoding now matches the actual encoding of the file.
Checking your file's encoding
Most code editors show the current file encoding in the status bar. In VS Code, for example, click the encoding label in the bottom-right corner to re-open the file with a different encoding or save it with a new one. When converting from windows-1252 to UTF-8, always verify that special characters survived the conversion by inspecting the rendered page.
The xml:lang attribute is a holdover from XHTML, where it was the standard way to declare the language of an element. In HTML5 (the text/html serialization), the lang attribute is the proper way to specify language. The HTML specification allows xml:lang for compatibility purposes, but only if it is accompanied by a lang attribute with an identical value. If xml:lang appears alone, or if its value doesn't match the lang attribute, the document is non-conforming.
This matters for several reasons. Screen readers and other assistive technologies rely on the lang attribute—not xml:lang—to determine pronunciation and language-specific behavior. Search engines also use lang for content indexing and language detection. Having xml:lang without lang means the language declaration may be ignored entirely, degrading both accessibility and SEO.
In modern HTML5 documents, there is rarely a reason to include xml:lang at all. The lang attribute alone covers all use cases. The only scenario where you might need both is if your document must be compatible with both HTML and XHTML parsers (polyglot markup), in which case the two attributes must carry the same value.
How to Fix
You have two options:
- Remove
xml:langand use onlylang(recommended for HTML5 documents). - Add a
langattribute that matches the existingxml:langvalue (for polyglot documents).
If you do keep both attributes, make sure the values are exactly the same—including case and subtags. For example, lang="en-US" must be paired with xml:lang="en-US", not xml:lang="en".
Examples
Incorrect: xml:lang without lang
<htmlxml:lang="en">
<head>
<title>My Page</title>
</head>
<body>
<pxml:lang="fr">Bonjour le monde</p>
</body>
</html>
This triggers the validation error because both the <html> and <p> elements have xml:lang but no lang attribute.
Incorrect: Mismatched values
<htmllang="en"xml:lang="en-US">
<head>
<title>My Page</title>
</head>
<body>
<p>Hello world</p>
</body>
</html>
Even though both attributes are present, the values "en" and "en-US" don't match, which is also invalid.
Correct: Using only lang (recommended)
<!DOCTYPE html>
<htmllang="en">
<head>
<title>My Page</title>
</head>
<body>
<plang="fr">Bonjour le monde</p>
</body>
</html>
This is the cleanest approach for HTML5 documents. The lang attribute is all you need.
Correct: Both attributes with matching values (polyglot)
<!DOCTYPE html>
<htmllang="en"xml:lang="en">
<head>
<title>My Page</title>
</head>
<body>
<plang="fr"xml:lang="fr">Bonjour le monde</p>
</body>
</html>
If you must keep xml:lang, every element that has it must also have lang with the exact same value.
The srcset attribute lets you provide multiple image sources so the browser can select the most appropriate one. There are two types of descriptors you can use in srcset: pixel density descriptors (e.g., 1x, 2x) and width descriptors (e.g., 400w, 800w). When you use pixel density descriptors, the browser already knows the relationship between each source — it simply picks the one matching the device's pixel ratio. But width descriptors work differently. They tell the browser the intrinsic pixel width of each image file, and the browser then needs to know how wide the image will actually be rendered on screen to calculate which file is the best fit. That's exactly what the sizes attribute provides.
The sizes attribute accepts a comma-separated list of media conditions paired with length values, plus a default length. For example, sizes="(max-width: 600px) 100vw, 50vw" tells the browser: "If the viewport is 600px wide or less, this image will occupy 100% of the viewport width; otherwise, it will occupy 50%." Armed with this information and the width descriptors in srcset, the browser can do the math and download only the most suitable image — before CSS or layout has even been calculated.
Why this matters
- Standards compliance: The HTML specification requires
sizeswheneversrcsetuses width descriptors. Omitting it produces invalid HTML. - Correct image selection: Without
sizes, browsers fall back to assuming the image will be100vwwide, which often leads to downloading unnecessarily large images on desktop layouts where the image is much smaller than the full viewport. - Performance: Serving oversized images wastes bandwidth and slows page load. A proper
sizesattribute ensures the browser downloads the smallest sufficient image. - Predictable behavior: Relying on the browser's fallback assumption (
100vw) makes your responsive images behave inconsistently and defeats the purpose of providing multiple candidates.
How to fix it
- Identify every
img(orsource) element that uses width descriptors insrcset. - Determine how wide the image will be displayed at different viewport sizes. You can inspect this with your browser's developer tools or by reviewing your CSS.
- Add a
sizesattribute that describes those widths using media conditions and CSS length values likepx,vw,em, orcalc()expressions.
Examples
Incorrect — missing sizes with width descriptors
<img
src="photo-400.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
alt="A mountain landscape">
This triggers the validation error because the browser sees width descriptors (400w, 800w, 1200w) but has no sizes attribute to determine the image's rendered width.
Correct — sizes attribute added
<img
src="photo-400.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
sizes="(max-width: 600px) 100vw, (max-width: 1000px) 50vw, 600px"
alt="A mountain landscape">
Here, the sizes attribute tells the browser:
- On viewports up to 600px wide, the image fills 100% of the viewport.
- On viewports between 601px and 1000px, the image fills 50% of the viewport.
- On larger viewports, the image is displayed at a fixed 600px width.
Correct — pixel density descriptors (no sizes needed)
<img
src="logo-1x.png"
srcset="logo-1x.png 1x, logo-2x.png 2x"
alt="Company logo">
When using pixel density descriptors (1x, 2x) instead of width descriptors, the sizes attribute is not required. The browser simply matches the descriptor to the device's pixel ratio.
Correct — using sizes with a <picture> element
<picture>
<source
srcset="hero-400.webp 400w, hero-800.webp 800w"
sizes="(max-width: 800px) 100vw, 800px"
type="image/webp">
<img
src="hero-800.jpg"
srcset="hero-400.jpg 400w, hero-800.jpg 800w"
sizes="(max-width: 800px) 100vw, 800px"
alt="Hero banner">
</picture>
The sizes attribute is required on both the source and img elements when either uses width descriptors in its srcset.
When you write an attribute value without quotes, the HTML parser follows strict rules about which characters are permitted. According to the WHATWG HTML specification, unquoted attribute values cannot contain spaces, ", ', =, <, >, or the backtick character. If any of these characters appear, the parser may misinterpret where the attribute value ends, potentially merging multiple attributes, breaking your markup, or producing unexpected rendering. URL query strings are an especially common trigger because they naturally contain &, =, and ? characters.
This is a problem for several reasons:
- Parsing errors: Browsers use error recovery when encountering malformed attributes, but different browsers may recover differently, leading to inconsistent behavior.
- Broken functionality: A URL like
page.html?id=5&sort=namein an unquotedhrefwill be parsed incorrectly — the browser may interpretsort=nameas a separate HTML attribute rather than part of the URL. - Attributes running together: If you forget a closing quote on one attribute, the parser may consume the next attribute's name and value as part of the first attribute's value, silently breaking your element.
- Standards compliance: Quoting all attribute values is a best practice recommended by the HTML specification for clarity and reliability.
The simplest and most robust fix is to always quote your attribute values using double quotes ("...") or single quotes ('...'). This eliminates ambiguity regardless of what characters the value contains.
Examples
Unquoted URL query string
A URL with query parameters in an unquoted href triggers this error because & and = confuse the parser:
<!-- ❌ Invalid: unquoted attribute value with query string -->
<ahref=https://example.com/search?q=html&lang=en>Search</a>
The parser may interpret lang=en as a separate HTML attribute on the <a> element rather than part of the URL.
<!-- ✅ Valid: attribute value properly quoted -->
<ahref="https://example.com/search?q=html&lang=en">Search</a>
Attributes running together
A missing closing quote causes the next attribute to be absorbed into the first:
<!-- ❌ Invalid: missing closing quote on class causes attributes to merge -->
<divclass="important id="main">Content</div>
The parser sees important id= as part of the class value, and "main" may be interpreted unpredictably.
<!-- ✅ Valid: both attributes properly quoted -->
<divclass="important"id="main">Content</div>
Unquoted values with special characters
Even simple values can cause issues if they contain characters not allowed in unquoted attributes:
<!-- ❌ Invalid: unquoted value with characters that confuse the parser -->
<inputtype=textvalue=helloworld>
<divclass=important"></div>
In the first example, world is parsed as a separate attribute, not part of the value. In the second, the stray " after important triggers the error.
<!-- ✅ Valid: all attribute values are quoted -->
<inputtype="text"value="hello world">
<divclass="important"></div>
Simple unquoted values (technically valid but discouraged)
While simple unquoted values without special characters are technically allowed by the spec, quoting them is strongly recommended for consistency and to avoid mistakes:
<!-- Technically valid but discouraged -->
<divclass=important></div>
<!-- ✅ Recommended: always use quotes -->
<divclass="important"></div>
Adopting the habit of always quoting attribute values prevents this entire class of validation errors and makes your HTML easier to read and maintain.
The X-UA-Compatible directive was introduced to control which rendering engine Internet Explorer would use when displaying a page. Setting it to a specific version like IE=10 locks the page into that version's rendering mode, even if the user has a newer version of Internet Explorer installed. This is problematic because it prevents the browser from using its most capable, standards-compliant rendering engine and can lead to unexpected layout or functionality issues.
The value IE=edge tells Internet Explorer to use the latest rendering engine available, which ensures the best possible standards compliance and feature support. The HTML specification and the W3C validator enforce this requirement because pinning to a specific IE version serves no forward-compatible purpose and can actively degrade the browsing experience.
It's worth noting that with Internet Explorer now being retired and replaced by Microsoft Edge, this meta tag is largely historical. However, if you include it at all, the validator requires that it be set to IE=edge. If your site no longer needs to support legacy versions of Internet Explorer, you can also simply remove the X-UA-Compatible declaration entirely — modern browsers ignore it.
How to fix it
- Find any
<meta http-equiv="X-UA-Compatible">tag in your HTML<head>. - Change the
contentattribute value from the specific version (e.g.,IE=10) toIE=edge. - If the directive is set as an HTTP response header on your server, update the header value there as well.
- Alternatively, remove the tag or header entirely if you no longer need IE compatibility.
Examples
Incorrect: pinned to a specific IE version
This triggers the validator error because IE=10 locks rendering to Internet Explorer 10 mode:
<metahttp-equiv="X-UA-Compatible"content="IE=10">
Other version-specific values that would also trigger this error include IE=9, IE=11, IE=EmulateIE10, and similar variations.
Correct: using IE=edge
Replace the version-specific value with IE=edge:
<metahttp-equiv="X-UA-Compatible"content="IE=edge">
Correct: full document example
When included in a complete HTML document, the X-UA-Compatible meta tag should appear early in the <head>, ideally right after the <meta charset> declaration:
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<metahttp-equiv="X-UA-Compatible"content="IE=edge">
<title>My Page</title>
</head>
<body>
<p>Page content goes here.</p>
</body>
</html>
Correct: removing the tag entirely
If IE support is no longer a concern, the simplest fix is to remove the meta tag altogether:
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>My Page</title>
</head>
<body>
<p>Page content goes here.</p>
</body>
</html>
Server-side HTTP header
If the X-UA-Compatible value is being sent as an HTTP response header rather than a meta tag, update your server configuration. For example, in Apache:
# Incorrect
Header set X-UA-Compatible "IE=10"
# Correct
Header set X-UA-Compatible "IE=edge"
Or in Nginx:
# Incorrect
add_header X-UA-Compatible "IE=10";
# Correct
add_header X-UA-Compatible "IE=edge";
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