HTML Guides
Learn how to identify and fix common HTML validation errors flagged by the W3C Validator — so your pages are standards-compliant and render correctly across every browser. Also check our Accessibility Guides.
The HTML specification defines <button> as a versatile interactive element, but its behavior changes depending on context. When a <button> is placed inside a <form> without a type attribute, it defaults to type="submit", which can cause unexpected form submissions. The validator flags this because relying on the implicit default is ambiguous and error-prone. Explicitly setting the type attribute makes the button’s intent clear to both developers and browsers.
The three valid values for the type attribute are:
- submit — submits the parent form’s data to the server.
- reset — resets all form controls to their initial values.
- button — performs no default action; behavior is defined via JavaScript.
When a <button> is given an ARIA role of checkbox, switch, or menuitemcheckbox, the validator expects an aria-checked attribute to accompany it. These roles describe toggle controls that have a checked or unchecked state, so assistive technologies need to know the current state. Without aria-checked, screen readers cannot communicate whether the control is on or off, making the interface inaccessible.
The aria-checked attribute accepts the following values:
- true — the control is checked or on.
- false — the control is unchecked or off.
- mixed — the control is in an indeterminate state (valid for checkbox and menuitemcheckbox roles only).
How to fix it
For standard buttons, add the type attribute with the appropriate value. If the button triggers JavaScript behavior and is not meant to submit a form, use type="button". If it submits a form, use type="submit" explicitly to make the intent clear.
For toggle buttons, ensure the <button> has both a role attribute (such as checkbox or switch) and an aria-checked attribute that reflects the current state. You should also include type="button" to prevent unintended form submission. Use JavaScript to toggle the aria-checked value when the user interacts with the button.
Examples
Missing type attribute
This triggers the validator warning because the type is not specified:
<form action="/search">
<input type="text" name="q">
<button>Search</button>
</form>
Fixed by adding an explicit type:
<form action="/search">
<input type="text" name="q">
<button type="submit">Search</button>
</form>
Button used outside a form without type
<button onclick="openMenu()">Menu</button>
Fixed by specifying type="button":
<button type="button" onclick="openMenu()">Menu</button>
Toggle button missing aria-checked
A button with role="switch" but no aria-checked attribute:
<button type="button" role="switch">Dark Mode</button>
Fixed by adding aria-checked:
<button type="button" role="switch" aria-checked="false">Dark Mode</button>
Checkbox-style toggle button
A button acting as a checkbox must include both role="checkbox" and aria-checked:
<button type="button" role="checkbox" aria-checked="false">
Enable notifications
</button>
Complete toggle example with all required attributes
<button type="button" role="switch" aria-checked="false" id="wifi-toggle">
Wi-Fi
</button>
<script>
document.getElementById("wifi-toggle").addEventListener("click", function () {
const isChecked = this.getAttribute("aria-checked") === "true";
this.setAttribute("aria-checked", String(!isChecked));
});
</script>
In this example, the type="button" prevents form submission, the role="switch" tells assistive technologies this is a toggle, and aria-checked is updated dynamically to reflect the current state. This ensures the button is fully accessible and passes validation.
The <dl> element represents a description list — a collection of terms (<dt>) paired with their descriptions (<dd>). According to the HTML specification, the permitted content of a <dl> is either groups of <dt> and <dd> elements directly, or <div> elements that wrap those groups. This <div> wrapping option exists specifically to help with styling, since it lets you apply CSS to a term-description pair as a unit.
However, these wrapper <div> elements are not general-purpose containers inside <dl>. The spec requires that each <div> inside a <dl> contains at least one <dt> or <dd> child. A <div> that is empty, or that contains other elements like <span>, <p>, or another <div>, violates this rule and produces a validation error.
This matters for several reasons. Screen readers and assistive technologies rely on the semantic structure of description lists to convey the relationship between terms and definitions. An empty or improperly structured <div> inside a <dl> breaks that semantic contract, potentially confusing both assistive technology and browsers. Keeping your markup valid also ensures consistent rendering across all browsers.
How to Fix It
- Ensure every <div> inside a <dl> contains at least one <dt> or <dd> — don’t leave wrapper <div> elements empty.
- Don’t put non-<dt>/<dd> content directly inside these <div> wrappers — elements like <span>, <p>, or nested <div> elements should be placed inside a <dt> or <dd>, not as siblings to them.
- Remove unnecessary <div> wrappers — if a <div> inside a <dl> serves no styling or grouping purpose, remove it entirely.
Examples
❌ Empty <div> inside a <dl>
<dl>
<div>
</div>
<div>
<dt>HTML</dt>
<dd>A markup language for structuring web content.</dd>
</div>
</dl>
The first <div> is empty and has no <dt> or <dd> child, triggering the error.
❌ <div> with only non-<dt>/<dd> children
<dl>
<div>
<p>This is a description list:</p>
</div>
<div>
<dt>CSS</dt>
<dd>A style sheet language for describing presentation.</dd>
</div>
</dl>
The first <div> contains a <p> element but no <dt> or <dd>, which is invalid.
✅ Each <div> contains <dt> and <dd> elements
<dl>
<div>
<dt>HTML</dt>
<dd>A markup language for structuring web content.</dd>
</div>
<div>
<dt>CSS</dt>
<dd>A style sheet language for describing presentation.</dd>
</div>
</dl>
✅ Using <dl> without <div> wrappers
If you don’t need <div> elements for styling, you can place <dt> and <dd> directly inside the <dl>:
<dl>
<dt>HTML</dt>
<dd>A markup language for structuring web content.</dd>
<dt>CSS</dt>
<dd>A style sheet language for describing presentation.</dd>
</dl>
✅ A <dt> with multiple <dd> descriptions in a <div>
A single term can have multiple descriptions. The <div> is valid as long as it contains at least one <dt> or <dd>:
<dl>
<div>
<dt>JavaScript</dt>
<dd>A programming language for the web.</dd>
<dd>Supports event-driven and functional programming.</dd>
</div>
</dl>
The aria-required attribute tells assistive technologies that a form field must be filled in before the form can be submitted. However, this attribute is only valid on elements that function as interactive widgets. A bare div has no implicit ARIA role, so assistive technologies have no context for what kind of input is expected. The validator flags this because an aria-required attribute on a generic div is effectively meaningless without additional ARIA attributes that define the element’s role and behavior.
This matters for several reasons:
- Accessibility: Screen readers and other assistive technologies rely on roles to understand how to present an element to users. Without a role, aria-required provides incomplete information.
- Standards compliance: The WAI-ARIA specification defines which attributes are allowed on which roles. Using aria-required without an established role violates these constraints.
- Browser behavior: Browsers may ignore or misinterpret ARIA attributes when they appear on elements that lack the proper role context.
How to fix it
Option 1: Use native semantic HTML (preferred)
Whenever possible, use native HTML form elements. They come with built-in accessibility semantics, keyboard interaction, and validation — no ARIA needed.
Replace a div with aria-required="true" with an appropriate form control using the native required attribute:
<input type="text" required>
<select required>
<option value="">Choose one</option>
<option value="1">Option 1</option>
</select>
<textarea required></textarea>
Option 2: Add an appropriate role attribute
When you must use a div as a custom widget (styled and enhanced with CSS and JavaScript), add the correct role attribute to give it semantic meaning. Choose the role that matches the widget’s actual behavior — don’t just pick one arbitrarily.
Common roles that support aria-required:
- combobox — a custom dropdown with text input
- listbox — a custom selection list
- radiogroup — a group of radio-like options
- spinbutton — a numeric stepper (also requires aria-valuemax, aria-valuemin, and aria-valuenow)
- textbox — a custom text input
Option 3: Add other qualifying ARIA attributes
For certain widgets like sliders or spinbuttons, you may need aria-valuemax, aria-valuemin, and aria-valuenow in addition to (or as part of) defining the role. These attributes inherently establish a widget context.
Examples
❌ Invalid: aria-required on a plain div
<div aria-required="true">
<div data-value="One">1</div>
<div data-value="Two">2</div>
<div data-value="Three">3</div>
</div>
This triggers the validation error because the div has no role or other ARIA attributes to define what kind of widget it is.
✅ Fixed: Adding the appropriate role
<div aria-required="true" role="radiogroup" aria-label="Pick a number">
<div role="radio" aria-checked="false" tabindex="0">1</div>
<div role="radio" aria-checked="false" tabindex="0">2</div>
<div role="radio" aria-checked="false" tabindex="0">3</div>
</div>
Adding role="radiogroup" gives the div a semantic identity. Note that child elements also need appropriate roles and attributes for the widget to be fully accessible.
✅ Fixed: Using native HTML instead
<fieldset>
<legend>Pick a number</legend>
<label><input type="radio" name="number" value="1" required> 1</label>
<label><input type="radio" name="number" value="2"> 2</label>
<label><input type="radio" name="number" value="3"> 3</label>
</fieldset>
This approach uses native radio buttons with the required attribute, eliminating the need for ARIA entirely. The browser handles accessibility, keyboard navigation, and form validation automatically.
✅ Fixed: A custom spinbutton with value attributes
<div role="spinbutton"
aria-required="true"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="50"
aria-label="Quantity"
tabindex="0">
50
</div>
For a spinbutton role, you must also provide aria-valuemin, aria-valuemax, and aria-valuenow to fully describe the widget’s state.
The HTML specification defines a strict content model for the <ul> (unordered list) element: its permitted content is zero or more <li> elements, optionally intermixed with <script> and <template> elements. A <div> is not among these allowed children, so placing one directly inside a <ul> produces a validation error.
This issue commonly arises when developers try to group or wrap list items for styling or layout purposes. For example, you might want to add a container around certain <li> elements for flexbox or grid alignment, or you might be using a templating system that injects wrapper <div> elements into your list markup.
Why this matters
- Browser parsing inconsistencies: When browsers encounter invalid nesting, they attempt to fix the DOM structure automatically, but different browsers may handle it differently. This can lead to unexpected rendering where list items appear outside their intended container or styles break unpredictably.
- Accessibility: Screen readers and assistive technologies rely on correct semantic structure to convey list relationships to users. A <div> breaking the <ul> → <li> relationship can cause assistive tools to misinterpret or skip list content entirely.
- Standards compliance: Invalid HTML can cause cascading parser errors — note that the validator message says “Suppressing further errors from this subtree,” meaning additional issues within that structure may be hidden from you.
How to fix it
- Move the <div> inside the <li>: If you need a wrapper for styling, place it inside the list item rather than around it.
- Remove the <div> entirely: If it serves no purpose, simply remove it and let the <li> elements sit directly inside the <ul>.
- Use CSS on the <li> elements: In many cases, you can apply the styles you need directly to the <li> elements without an extra wrapper.
- Use role="list" on a <div> parent: If your layout truly requires <div> wrappers, consider restructuring with ARIA roles, though native semantic elements are always preferred.
Examples
❌ Incorrect: <div> as a direct child of <ul>
<ul>
<div>
<li>Apples</li>
<li>Bananas</li>
</div>
<div>
<li>Carrots</li>
<li>Dates</li>
</div>
</ul>
✅ Correct: Move the <div> inside each <li>
<ul>
<li><div>Apples</div></li>
<li><div>Bananas</div></li>
<li><div>Carrots</div></li>
<li><div>Dates</div></li>
</ul>
✅ Correct: Remove the <div> entirely
<ul>
<li>Apples</li>
<li>Bananas</li>
<li>Carrots</li>
<li>Dates</li>
</ul>
❌ Incorrect: Using a <div> as a styling wrapper around list items
<ul class="product-list">
<div class="row">
<li>Product A</li>
<li>Product B</li>
<li>Product C</li>
</div>
</ul>
✅ Correct: Apply layout styles directly to the <ul> and <li> elements
<ul class="product-list row">
<li>Product A</li>
<li>Product B</li>
<li>Product C</li>
</ul>
❌ Incorrect: Template or component wrapper inserting a <div>
This often happens in frameworks where a component renders a wrapping <div>:
<ul>
<div class="list-item-wrapper">
<li>Item 1</li>
</div>
<div class="list-item-wrapper">
<li>Item 2</li>
</div>
</ul>
✅ Correct: Move the wrapper class to the <li> itself
<ul>
<li class="list-item-wrapper">Item 1</li>
<li class="list-item-wrapper">Item 2</li>
</ul>
The same rule applies to <ol> (ordered list) elements — they share the same content model restriction. Always ensure that <li> is the direct child of <ul> or <ol>, and place any additional wrapper elements inside the <li> rather than around it.
According to the HTML specification, the <dl> element’s content model requires one or more groups, where each group consists of one or more <dt> elements followed by one or more <dd> elements. Alternatively, these groups can be wrapped in <div> elements for styling purposes. An empty <dl> or one missing either side of the pairing is invalid.
This matters for several reasons. Screen readers and other assistive technologies rely on the proper structure of description lists to convey the relationship between terms and their descriptions. When the structure is incomplete, these tools cannot communicate the intended meaning to users. Browsers may also render malformed description lists inconsistently, leading to unpredictable layouts.
To fix this issue, make sure every <dl> contains at least one <dt> element paired with at least one <dd> element. If you only have a <dd>, add the corresponding <dt>. If you only have a <dt>, add the corresponding <dd>. If the list is empty or you don’t have content for both parts, consider whether a <dl> is the right element for your use case—a <ul> or <p> might be more appropriate.
Examples
Missing <dt> element
A <dd> without its corresponding <dt> triggers this error:
<dl>
<dd>A box without hinges, key, or lid, yet golden treasure inside is hid.</dd>
</dl>
Add the missing <dt> to complete the group:
<dl>
<dt>Egg</dt>
<dd>A box without hinges, key, or lid, yet golden treasure inside is hid.</dd>
</dl>
Missing <dd> element
A <dt> without a following <dd> also triggers this error:
<dl>
<dt>Egg</dt>
</dl>
Add the missing <dd> to provide the description:
<dl>
<dt>Egg</dt>
<dd>A box without hinges, key, or lid, yet golden treasure inside is hid.</dd>
</dl>
Empty <dl> element
An empty description list is invalid:
<dl></dl>
Either populate it with a complete term-description group or remove the element entirely.
Multiple terms and descriptions
A single <dt> can have multiple <dd> elements, and multiple <dt> elements can share the same <dd>. Both are valid:
<dl>
<dt>Egg</dt>
<dt>Ova</dt>
<dd>A box without hinges, key, or lid, yet golden treasure inside is hid.</dd>
<dd>An oval body laid by birds, reptiles, and other creatures.</dd>
</dl>
Using <div> wrappers
You can wrap each name-value group in a <div> for styling purposes, but each <div> must still contain the required <dt> and <dd> pairing:
<dl>
<div>
<dt>Egg</dt>
<dd>A box without hinges, key, or lid, yet golden treasure inside is hid.</dd>
</div>
<div>
<dt>Sun</dt>
<dd>An eye in a blue face saw an eye in a green face.</dd>
</div>
</dl>
The <dl> element represents a description list — a collection of name-value groups where <dt> elements provide the terms (or names) and <dd> elements provide the associated descriptions (or values). According to the HTML specification, the content model for <dl> requires that each <dt> group be followed by one or more <dd> elements. A <dl> with only <dt> elements and no <dd> elements is invalid HTML.
This matters for several reasons. Assistive technologies like screen readers rely on the proper <dt>/<dd> pairing to convey the relationship between terms and their descriptions. A description list without descriptions is semantically meaningless — it’s like having a dictionary with words but no definitions. Browsers may also render the list inconsistently when the expected structure is incomplete.
Common causes of this error include:
- Accidentally using <dl> and <dt> to create a simple list instead of using <ul> or <ol>.
- Dynamically generated content where the <dd> elements are missing due to empty data.
- Incomplete markup where the developer forgot to add the description elements.
To fix this issue, make sure every <dt> element inside a <dl> is followed by at least one <dd> element. If you don’t actually need a description list, consider using a different element like <ul> for simple lists.
Examples
Invalid: <dl> with only <dt> and no <dd>
<dl>
<dt>The Meaning of Life</dt>
</dl>
This is invalid because the <dt> term has no corresponding <dd> description.
Fixed: Adding a <dd> element
<dl>
<dt>The Meaning of Life</dt>
<dd>A philosophical question about the significance of existence.</dd>
</dl>
Invalid: Multiple terms without any descriptions
<dl>
<dt>HTML</dt>
<dt>CSS</dt>
<dt>JavaScript</dt>
</dl>
This triggers the error because none of the terms have associated descriptions.
Fixed: Each term with a description
<dl>
<dt>HTML</dt>
<dd>A markup language for structuring web content.</dd>
<dt>CSS</dt>
<dd>A style sheet language for describing presentation.</dd>
<dt>JavaScript</dt>
<dd>A programming language for web interactivity.</dd>
</dl>
Valid: Multiple terms sharing a single description
Multiple <dt> elements can share a single <dd>, which is useful for synonyms or aliases:
<dl>
<dt>Laptop</dt>
<dt>Notebook</dt>
<dd>A portable personal computer with a clamshell form factor.</dd>
</dl>
Valid: A single term with multiple descriptions
A <dt> can also be followed by multiple <dd> elements:
<dl>
<dt>Python</dt>
<dd>A large constricting snake.</dd>
<dd>A high-level programming language.</dd>
</dl>
Valid: Using <div> to wrap name-value groups
The HTML spec allows wrapping each <dt>/<dd> group in a <div> for styling purposes. Each <div> must still contain at least one <dd>:
<dl>
<div>
<dt>Name</dt>
<dd>Jane Doe</dd>
</div>
<div>
<dt>Email</dt>
<dd>jane@example.com</dd>
</div>
</dl>
Alternative: Use <ul> when descriptions aren’t needed
If you only need a list of items without descriptions, a <dl> is the wrong element. Use an unordered list instead:
<ul>
<li>HTML</li>
<li>CSS</li>
<li>JavaScript</li>
</ul>
The <title> element is a required child of <head> according to the HTML specification. It defines the document’s title, which appears in the browser tab, is used as the default name when bookmarking the page, and is displayed as the clickable heading in search engine results. Omitting it violates the HTML standard and triggers this validation error.
Beyond standards compliance, the <title> element is critical for accessibility. Screen readers announce the page title when a user navigates to a new page or switches between tabs, giving them immediate context about where they are. A missing title forces assistive technology users to explore the page content to understand its purpose. Search engines also rely heavily on the <title> for indexing and ranking, so omitting it can hurt discoverability.
There are several common causes for this error:
- The <title> element is simply missing. This often happens in boilerplate code or quick prototypes where it’s overlooked.
- Duplicate <head> sections. If your HTML contains two <head> elements (for example, from a copy-paste error or a templating mistake), the validator may flag the second one as missing a <title>.
- The <title> is placed outside <head>. If the <title> element accidentally ends up in the <body> or before the <head>, the validator won’t count it as a child of <head>.
- The <title> is empty. While an empty <title> element (<title></title>) may not trigger this specific error, some validators will flag it separately. Always include descriptive text.
Examples
Missing <title> element
This triggers the validation error because the <head> has no <title>:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<h1>Welcome</h1>
</body>
</html>
<title> placed outside <head>
Here the <title> exists but is in the wrong location, so the <head> is still considered to be missing it:
<!DOCTYPE html>
<html lang="en">
<title>My Page</title>
<head>
<meta charset="utf-8">
</head>
<body>
<h1>Welcome</h1>
</body>
</html>
Duplicate <head> sections
A templating error or copy-paste mistake can introduce a second <head>, which lacks its own <title>:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1>Welcome</h1>
</body>
</html>
Correct usage
Place a single <title> element with descriptive text inside the <head>:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Welcome — My Website</title>
</head>
<body>
<h1>Welcome</h1>
</body>
</html>
Tips for a good <title>
- Keep it concise but descriptive — aim for roughly 50–60 characters.
- Make it unique for each page on your site. Avoid generic titles like “Untitled” or “Page”.
- Front-load the most important information. For example, Contact Us — My Company is more useful than My Company — Contact Us when users scan many browser tabs.
- Avoid duplicating the <h1> verbatim; the title should provide broader context (such as including the site name), while the <h1> focuses on page-specific content.
Every <img> element must include at least a src or a srcset attribute to be valid HTML.
The <img> element exists to embed an image into the document, and it needs to know where that image is. The src attribute provides a single URL for the image, while srcset lets you offer multiple image sources for different screen sizes or resolutions.
You might run into this error when using JavaScript to set the image source dynamically, or when using lazy-loading libraries that store the URL in a data- attribute like data-src. While those techniques work at runtime, they produce invalid HTML because the validator still expects src or srcset to be present in the markup.
If you genuinely don’t have a source yet, you can use a placeholder or a transparent pixel as the src value.
Invalid Example
<img alt="A cute cat" loading="lazy" data-src="cat.jpg">
Valid Examples
Using src:
<img src="cat.jpg" alt="A cute cat">
Using srcset:
<img srcset="cat-400.jpg 400w, cat-800.jpg 800w"
sizes="(max-width: 600px) 400px, 800px"
alt="A cute cat">
Using both src and a lazy-loading data-src (keeps the markup valid while still supporting lazy loading):
<img src="placeholder.jpg" data-src="cat.jpg" alt="A cute cat">
The src attribute is one of two required attributes on every <img> element (the other being alt). The HTML specification mandates src because an image element without a source has no meaningful content to render. Omitting it produces invalid markup and leads to unpredictable browser behavior — some browsers may display a broken image icon, while others may render nothing at all.
This issue commonly occurs in a few scenarios:
- Templating placeholders — A developer adds an <img> tag intending to populate the src dynamically but forgets to set a default value.
- Lazy loading implementations — Some lazy-loading scripts store the real URL in a data-src attribute and omit src entirely, which results in invalid HTML.
- Incomplete markup — The attribute is simply forgotten during development.
Beyond validation, a missing src attribute causes accessibility problems. Screen readers rely on well-formed <img> elements to convey image information to users. A malformed image element can confuse assistive technologies and degrade the user experience.
How to fix it
- Add a src attribute with a valid URL pointing to your image.
- Always include an alt attribute as well — it’s also required and provides a text alternative for the image.
- If you’re using lazy loading and want to defer the actual image source, set src to a lightweight placeholder (such as a tiny transparent image or a low-quality preview) and use the native loading="lazy" attribute instead of removing src.
Examples
❌ Missing src attribute
<img alt="A sunset over the ocean">
This triggers the validation error because src is absent.
❌ Source stored only in a data- attribute
<img data-src="/images/photo.jpg" alt="A sunset over the ocean">
While data-src is a valid custom data attribute, it does not satisfy the requirement for src. The validator will still report the error.
✅ Correct usage with src and alt
<img src="/images/photo.jpg" alt="A sunset over the ocean">
✅ Lazy loading with a placeholder src
<img
src="/images/photo-placeholder.jpg"
data-src="/images/photo-full.jpg"
alt="A sunset over the ocean"
loading="lazy">
Here, src points to a small placeholder image so the markup stays valid, while data-src holds the full-resolution URL for your lazy-loading script to swap in.
✅ Native lazy loading (no JavaScript needed)
<img src="/images/photo.jpg" alt="A sunset over the ocean" loading="lazy">
Modern browsers support the loading="lazy" attribute natively, so you can keep a valid src and still defer off-screen images without any custom scripting.
The <link> element defines a relationship between the current document and an external resource. It’s most often used in the <head> to load stylesheets, declare icons, specify canonical URLs, or provide metadata. The HTML specification requires that at least one of href, itemprop, property, rel, or resource be present on any <link> element. A bare <link> tag with none of these attributes has no defined purpose and is therefore invalid.
This validation error typically occurs in a few scenarios:
- A <link> element was added as a placeholder and never completed.
- Attributes were accidentally removed or misspelled during editing.
- A templating engine or build tool generated an incomplete <link> tag.
- The rel attribute was omitted when only href was technically sufficient, but the developer intended to specify a relationship.
While browsers may silently ignore an empty <link> element, leaving it in your markup creates clutter, signals incomplete code, and violates the HTML standard. Keeping your HTML valid ensures predictable behavior across browsers and assistive technologies.
How to fix it
Check every <link> element in your document and make sure it includes at least one of the required attributes. In practice, most <link> elements should have both rel and href:
- rel — specifies the relationship (e.g., stylesheet, icon, canonical, preconnect).
- href — provides the URL of the linked resource.
- itemprop — used for microdata markup.
- property — used for RDFa or Open Graph metadata.
- resource — used in RDFa to identify a resource by URI.
If a <link> element has no valid purpose, remove it entirely.
Examples
Invalid: <link> with no required attributes
<link type="text/css">
The type attribute alone does not satisfy the requirement. The validator will flag this element.
Invalid: <link> with only crossorigin
<link crossorigin="anonymous">
crossorigin is not one of the required attributes, so this is still invalid.
Valid: stylesheet with rel and href
<link rel="stylesheet" href="styles.css">
Valid: favicon with rel and href
<link rel="icon" href="/favicon.ico" type="image/x-icon">
Valid: preconnect hint
<link rel="preconnect" href="https://fonts.googleapis.com">
Valid: canonical URL
<link rel="canonical" href="https://example.com/page">
Valid: Open Graph metadata with property
<link property="og:image" href="https://example.com/image.png">
Valid: microdata with itemprop
<link itemprop="url" href="https://example.com">
Full document example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
<link rel="stylesheet" href="styles.css">
<link rel="icon" href="/favicon.ico">
<link rel="canonical" href="https://example.com/my-page">
</head>
<body>
<h1>Hello, world!</h1>
</body>
</html>
Each <link> element in this document has both a rel and an href attribute, making them valid and clearly communicating their purpose to browsers and validators.
The <link> element connects your HTML document to external resources like stylesheets, icons, fonts, and prefetched pages. According to the HTML specification, a <link> element must include at least one of rel, itemprop, or property so that its purpose is clearly defined. A bare <link> with only an href is meaningless—it points to a resource but doesn’t explain what that resource is for. The validator raises this error to enforce that every <link> carries semantic meaning.
This matters for several reasons. Browsers rely on these attributes to decide how to handle the linked resource. A <link> with rel="stylesheet" triggers CSS loading, while rel="icon" tells the browser to use the resource as a favicon. Without one of the required attributes, browsers may ignore the element entirely, leading to missing styles, icons, or other resources. It also affects accessibility tools and search engines that parse your markup for structured data.
Understanding the three attributes
-
rel — The most common attribute. It defines the relationship between your document and the linked resource. Examples include stylesheet, icon, preconnect, preload, canonical, and alternate. Most <link> elements in practice use rel.
-
itemprop — Used when the <link> element is part of an HTML Microdata structure. It specifies a property name within an itemscope, linking to a URL as the property’s value. This is commonly seen with Schema.org vocabularies.
-
property — Used with RDFa metadata (such as Open Graph tags). It defines a metadata property for the document, like og:image or schema:citation.
You only need one of these three attributes to satisfy the requirement, though you can combine them when appropriate.
Examples
Invalid: <link> with no relationship attribute
This triggers the validation error because the element has no rel, itemprop, or property attribute:
<head>
<title>My Page</title>
<link href="styles.css">
</head>
Fixed: adding rel for a stylesheet
<head>
<title>My Page</title>
<link rel="stylesheet" href="styles.css">
</head>
Fixed: common uses of rel
<head>
<title>My Page</title>
<link rel="stylesheet" href="styles.css">
<link rel="icon" href="favicon.ico">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="canonical" href="https://example.com/page">
</head>
Fixed: using itemprop with Microdata
When a <link> appears inside an element with itemscope, use itemprop to define a property that takes a URL value:
<div itemscope itemtype="https://schema.org/Article">
<h2 itemprop="name">Understanding HTML Validation</h2>
<link itemprop="mainEntityOfPage" href="https://example.com/article">
</div>
Fixed: using property with RDFa / Open Graph
Open Graph meta tags for social sharing commonly use the property attribute. While <meta> is more typical for Open Graph, <link> with property is valid for URL-type values:
<head>
<title>My Page</title>
<link property="og:image" href="https://example.com/image.jpg">
<link property="schema:citation" href="https://example.com/source.html">
</head>
Invalid: typo or misplaced attribute
Sometimes this error appears because of a misspelled attribute name:
<head>
<title>My Page</title>
<link rел="stylesheet" href="styles.css">
</head>
Double-check that rel is spelled correctly and isn’t accidentally omitted when copying markup from templates or code snippets.
Quick fix checklist
- Linking to a stylesheet, icon, font, or other resource? Add the appropriate rel value.
- Defining Microdata properties? Use itemprop within an itemscope context.
- Adding RDFa or Open Graph metadata? Use property with the correct vocabulary prefix.
- Still seeing the error? Check for typos in the attribute name or ensure the attribute isn’t accidentally empty.
The <meta> element provides metadata about the HTML document — information that isn’t displayed on the page but is used by browsers, search engines, and other web services. According to the HTML specification, a <meta> tag without any of the recognized attributes is meaningless. The validator flags this because a bare <meta> element (or one with only unrecognized attributes) provides no useful metadata and likely indicates an error or incomplete tag.
This issue commonly occurs when a <meta> tag is left empty by accident, when an attribute name is misspelled (e.g., naem instead of name), or when a required attribute is accidentally deleted during editing.
Most <meta> use cases fall into a few patterns, each requiring specific attribute combinations:
- charset — Used alone to declare the document’s character encoding.
- name + content — Used together to define named metadata like descriptions, viewport settings, or author information.
- http-equiv + content — Used together to simulate an HTTP response header.
- property + content — Used together for Open Graph and similar RDFa-based metadata.
- itemprop + content — Used together for microdata annotations.
Note that content alone is not sufficient — it must be paired with name, http-equiv, property, or itemprop to have meaning.
Examples
Incorrect: bare <meta> tag with no attributes
This triggers the validation error because the <meta> element has no recognized attributes:
<meta>
Incorrect: misspelled attribute
A typo in the attribute name means the validator doesn’t recognize it:
<meta nane="description" content="An example page.">
Incorrect: content without a pairing attribute
The content attribute alone is not enough — it needs name, http-equiv, property, or itemprop:
<meta content="some value">
Correct: character encoding with charset
<meta charset="UTF-8">
Correct: named metadata with name and content
<meta name="description" content="A brief description of the webpage.">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="author" content="Jane Doe">
Correct: HTTP-equivalent with http-equiv and content
<meta http-equiv="X-UA-Compatible" content="IE=edge">
Correct: Open Graph metadata with property and content
<meta property="og:title" content="My Page Title">
<meta property="og:description" content="A summary of the page content.">
Correct: microdata with itemprop and content
<meta itemprop="name" content="Product Name">
Full document example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="A brief description of the webpage.">
<meta property="og:title" content="My Page Title">
<title>Example Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
How to fix
- Find the flagged <meta> tag in your HTML source at the line number the validator reports.
- Check for typos in attribute names — make sure name, charset, http-equiv, property, or itemprop is spelled correctly.
- Add the missing attribute. Determine what the <meta> tag is supposed to do and add the appropriate attribute(s). If you can’t determine its purpose, it may be safe to remove it entirely.
- Ensure proper pairing. If you’re using content, make sure it’s paired with name, http-equiv, property, or itemprop. The charset attribute is the only one that works on its own without content.
The <meta> element is used to provide metadata about an HTML document. According to the HTML specification, a <meta> element must serve a specific purpose, and that purpose is determined by its attributes. A bare <meta> tag or one with only a charset attribute in the wrong context will trigger this validation error.
There are several valid patterns for <meta> elements:
- name + content: Standard metadata pairs (e.g., description, viewport, author).
- http-equiv + content: Pragma directives that affect how the browser processes the page.
- charset: Declares the document’s character encoding (only valid once, in the <head>).
- itemprop + content: Microdata metadata, which can appear in both <head> and <body>.
- property + content: Used for Open Graph and RDFa metadata.
When a <meta> tag doesn’t match any of these valid patterns, the validator raises this error. The most common causes are:
- Forgetting the content attribute when using name or property.
- Using non-standard attributes without the required ones (e.g., only specifying a custom attribute).
- Placing a charset meta in the <body>, where it’s not valid.
- Typos in attribute names like contents instead of content.
This matters for standards compliance and can also affect SEO and social sharing. Search engines and social media crawlers rely on properly formed <meta> tags to extract page information. Malformed tags may be silently ignored, meaning your metadata won’t take effect.
Examples
Incorrect: <meta> with name but no content
<head>
<meta charset="utf-8">
<title>My Page</title>
<meta name="description">
</head>
The <meta name="description"> tag is missing its content attribute, so the validator reports the error.
Correct: <meta> with both name and content
<head>
<meta charset="utf-8">
<title>My Page</title>
<meta name="description" content="A brief description of the page.">
</head>
Incorrect: <meta> with property but no content
<head>
<meta charset="utf-8">
<title>My Page</title>
<meta property="og:title">
</head>
Correct: Open Graph <meta> with property and content
<head>
<meta charset="utf-8">
<title>My Page</title>
<meta property="og:title" content="My Page">
</head>
Incorrect: <meta> with only a non-standard attribute
<head>
<meta charset="utf-8">
<title>My Page</title>
<meta name="theme-color" value="#ff0000">
</head>
Here, value is not a valid attribute for <meta>. The correct attribute is content.
Correct: Using content instead of value
<head>
<meta charset="utf-8">
<title>My Page</title>
<meta name="theme-color" content="#ff0000">
</head>
Incorrect: Bare <meta> tag with no meaningful attributes
<head>
<meta charset="utf-8">
<title>My Page</title>
<meta>
</head>
A <meta> element with no attributes serves no purpose and should be removed entirely.
Correct: Using itemprop in the <body>
The itemprop attribute allows <meta> to be used within the <body> as part of microdata:
<body>
<div itemscope itemtype="https://schema.org/Product">
<span itemprop="name">Example Product</span>
<meta itemprop="sku" content="12345">
</div>
</body>
The <meta> element is used to provide machine-readable metadata about an HTML document, such as its description, character encoding, viewport settings, or social media information. The HTML specification defines several valid forms for <meta>, and most of them require a content attribute to supply the metadata’s value.
This error typically appears when a <meta> tag includes a name or http-equiv attribute but is missing the corresponding content attribute. It can also appear when a <meta> tag has no recognizable attributes at all, or when the property attribute (used by Open Graph / RDFa metadata) is present without content.
A <meta> element must use one of these valid attribute patterns:
- name + content — Named metadata (e.g., description, author, viewport)
- http-equiv + content — Pragma directives (e.g., refresh, content-type)
- charset — Character encoding declaration (no content needed)
- property + content — RDFa/Open Graph metadata (e.g., og:title)
- itemprop + content — Microdata metadata
Without the proper combination, browsers and search engines cannot correctly interpret the metadata, which can hurt SEO, accessibility, and proper page rendering. For example, a <meta name="description"> tag without content provides no description to search engines, and a <meta name="viewport"> without content won’t configure the viewport on mobile devices.
Examples
❌ Missing content attribute
<head>
<meta charset="utf-8">
<title>My Page</title>
<meta name="description">
<meta name="viewport">
</head>
Both <meta> tags with name are missing their required content attribute.
❌ Empty or bare <meta> tag
<head>
<meta charset="utf-8">
<title>My Page</title>
<meta>
</head>
A <meta> element with no attributes at all is invalid.
❌ Open Graph tag missing content
<head>
<meta charset="utf-8">
<title>My Page</title>
<meta property="og:title">
</head>
✅ Correct usage with name and content
<head>
<meta charset="utf-8">
<title>My Page</title>
<meta name="description" content="A brief description of the page">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
✅ Correct usage with http-equiv and content
<meta http-equiv="refresh" content="30">
✅ Correct usage with Open Graph property and content
<meta property="og:title" content="My Page Title">
<meta property="og:description" content="A description for social sharing">
✅ Correct charset declaration (no content needed)
<meta charset="utf-8">
The charset form is the one exception where content is not required, because the character encoding is specified directly in the charset attribute value.
How to fix
- Find the flagged <meta> tag in your HTML source at the line number reported by the validator.
- Determine what type of metadata it represents. Does it have a name, http-equiv, or property attribute?
- Add the missing content attribute with an appropriate value. If you intended the metadata to be empty, use content="", though it’s generally better to either provide a meaningful value or remove the tag entirely.
- If the <meta> tag has no attributes at all, decide what metadata you intended to provide and add the correct attribute combination, or remove the element.
The <meta> element is most commonly used inside the <head> section to define metadata like character encoding, viewport settings, or descriptions. Inside <head>, attributes like charset, http-equiv, and name are perfectly valid. However, the HTML specification also allows <meta> to appear inside the <body> — but only under specific conditions.
When a <meta> element appears in the <body>, it must have either an itemprop attribute (for microdata) or a property attribute (for RDFa). It must also have a content attribute. Additionally, it cannot use http-equiv, charset, or name attributes in this context. These rules exist because the only valid reason to place a <meta> tag in the <body> is to embed machine-readable metadata as part of a structured data annotation — not to define document-level metadata.
Why this matters
- Standards compliance: The HTML living standard explicitly restricts which attributes <meta> can use depending on its placement. Violating this produces invalid HTML.
- Browser behavior: Browsers may ignore or misinterpret <meta> elements that appear in the <body> without proper attributes. For example, a <meta http-equiv="content-type"> tag inside the <body> will have no effect on character encoding, since that must be determined before the body is parsed.
- SEO and structured data: Search engines rely on correctly structured microdata and RDFa. A <meta> element in the body without itemprop or property won’t contribute to any structured data and serves no useful purpose.
Common causes
- Misplaced <meta> tags: A <meta> element meant for the <head> (such as <meta http-equiv="..."> or <meta name="description">) has accidentally been placed inside the <body>. This can happen due to an unclosed <head> tag, a CMS inserting tags in the wrong location, or simply copying markup into the wrong section.
- Missing itemprop or property: A <meta> element inside the <body> is being used for structured data but is missing the required itemprop or property attribute.
Examples
Incorrect: <meta> with http-equiv inside the <body>
This <meta> tag belongs in the <head>, not the <body>:
<body>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<form>
<input type="text" name="q">
</form>
</body>
Fixed: Move the <meta> to the <head>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>My Page</title>
</head>
<body>
<form>
<input type="text" name="q">
</form>
</body>
Incorrect: <meta> in the <body> without itemprop or property
<div itemscope itemtype="https://schema.org/Offer">
<span itemprop="price">9.99</span>
<meta content="USD">
</div>
The <meta> element is missing the itemprop attribute, so the validator reports the error.
Fixed: Add the itemprop attribute
<div itemscope itemtype="https://schema.org/Offer">
<span itemprop="price">9.99</span>
<meta itemprop="priceCurrency" content="USD">
</div>
Correct: Using property for RDFa
The property attribute is also valid for <meta> elements in the <body> when using RDFa:
<div vocab="https://schema.org/" typeof="Event">
<span property="name">Concert</span>
<meta property="startDate" content="2025-08-15T19:00">
</div>
Incorrect: <meta name="..."> inside the <body>
The name attribute is only valid on <meta> elements inside the <head>:
<body>
<meta name="author" content="Jane Doe">
<p>Welcome to my site.</p>
</body>
Fixed: Move it to the <head>
<head>
<title>My Site</title>
<meta name="author" content="Jane Doe">
</head>
<body>
<p>Welcome to my site.</p>
</body>
When the validator parses your HTML (especially in XHTML mode or when serialized as XML), every element name must conform to the XML 1.0 naming rules. These rules require that element names begin with a letter (a–z, A–Z) or an underscore (_), followed by any combination of letters, digits, hyphens (-), underscores, periods (.), or combining characters. Characters like spaces, angle brackets, slashes, or other special symbols within a tag name make it unrepresentable in XML 1.0.
This error most commonly occurs due to:
- Typos in tag names — accidentally inserting a space, extra character, or symbol into a tag name.
- Malformed closing tags — forgetting the slash or placing characters incorrectly in a closing tag.
- Template syntax errors — template engine placeholders leaking into the final HTML output.
- Copy-paste issues — invisible or non-ASCII characters sneaking into tag names from rich-text editors.
This matters because browsers may not parse malformed tags as intended, leading to broken layouts or missing content. Screen readers and assistive technologies rely on well-formed markup to interpret page structure. Additionally, any system that processes your HTML as XML (such as RSS feed generators, EPUB renderers, or XHTML-serving environments) will reject documents with invalid element names entirely.
How to Fix
- Inspect the flagged line — look carefully at the element name the validator is complaining about. Check for stray characters, spaces, or symbols.
- Correct any typos — replace the malformed tag with the correct HTML element name.
- Validate template output — if you use a templating engine, ensure the rendered HTML doesn’t contain unprocessed template tokens inside tag names.
- Check for invisible characters — paste the tag name into a plain-text editor or use a hex viewer to spot hidden characters.
Examples
Typo with a space in the tag name
A space inside the tag name creates an invalid element name:
<!-- Wrong: space in the element name -->
<di v class="container">
<p>Hello world</p>
</di v>
Fix by removing the accidental space:
<!-- Correct -->
<div class="container">
<p>Hello world</p>
</div>
Special character in a tag name
An accidental special character makes the name unrepresentable in XML 1.0:
<!-- Wrong: stray hash character in the tag name -->
<s#ection>
<h2>About</h2>
</s#ection>
Fix by using the correct element name:
<!-- Correct -->
<section>
<h2>About</h2>
</section>
Malformed closing tag
A missing or misplaced slash can produce a garbled tag name:
<!-- Wrong: slash is in the wrong place -->
<p>Some text<p/>
Fix with a properly formed closing tag:
<!-- Correct -->
<p>Some text</p>
Template placeholder leaking into output
Unprocessed template syntax can produce invalid element names in the rendered HTML:
<!-- Wrong: unresolved template variable in element name -->
<{{tagName}}>Content</{{tagName}}>
Ensure your template engine resolves the variable before serving the HTML. The rendered output should be:
<!-- Correct: after template processing -->
<article>Content</article>
The <object> element embeds external resources such as images, videos, PDFs, or other media into a page. The data attribute specifies the URL of the resource, while the type attribute declares its MIME type (e.g., "application/pdf", "image/svg+xml", "video/mp4"). According to the HTML specification, the element must have at least one of these attributes present — without either, the element is meaningless because the browser cannot determine what to fetch or how to render it.
While the validator requires at least one of these attributes, best practice is to include both. Here’s why:
- Without data, the browser has no resource to load.
- Without type, the browser must guess the content type from the server response, which can lead to incorrect rendering or security issues.
- With both, the browser knows exactly what to fetch and how to handle it before the resource even begins downloading, improving performance and reliability.
Including both attributes also benefits accessibility. Assistive technologies use the type attribute to determine how to present the content to users. Without it, screen readers and other tools may not be able to convey useful information about the embedded resource. You should also always provide fallback content inside the <object> element for browsers or devices that cannot render the embedded resource.
Examples
Missing both attributes (triggers the error)
<object width="600" height="400">
<p>Fallback content here.</p>
</object>
The validator reports this error because neither data nor type is present. The browser has no information about what this <object> should display.
Missing type attribute (valid but not recommended)
<object data="example.pdf" width="600" height="400">
<p>Your browser does not support PDFs. <a href="example.pdf">Download the PDF</a>.</p>
</object>
This passes validation because data is present, but the browser must determine the content type on its own. Adding type is strongly recommended.
Missing data attribute (valid but limited)
<object type="application/pdf" width="600" height="400">
<p>No PDF to display.</p>
</object>
This also passes validation since type is present, but without data there is no resource to load. This pattern is uncommon and generally not useful on its own.
Correct: both attributes provided
<object data="example.pdf" type="application/pdf" width="600" height="400">
<p>Your browser does not support PDFs. <a href="example.pdf">Download the PDF</a>.</p>
</object>
Correct: embedding an SVG image
<object data="diagram.svg" type="image/svg+xml" width="300" height="200">
<img src="diagram.png" alt="Architecture diagram showing the system components">
</object>
This example also demonstrates good fallback practice — if the browser cannot render the SVG via <object>, it falls back to a regular <img> element with descriptive alt text.
Correct: embedding a video
<object data="intro.mp4" type="video/mp4" width="640" height="360">
<p>Your browser cannot play this video. <a href="intro.mp4">Download it instead</a>.</p>
</object>
Quick reference of common MIME types
| Resource type | type value |
|---|---|
| PDF document | application/pdf |
| SVG image | image/svg+xml |
| MP4 video | video/mp4 |
| HTML page | text/html |
| Flash (legacy) | application/x-shockwave-flash |
To resolve this validation error, ensure every <object> element includes at least a data or type attribute. For the best results across browsers and assistive technologies, always provide both along with meaningful fallback content inside the element.
The <option> element represents a choice within a <select> dropdown, a <datalist>, or an <optgroup>. According to the HTML specification, every option must have a name — the text that is displayed to the user. This name can come from one of two sources: the text content between the opening <option> and closing </option> tags, or the label attribute on the element. If neither is provided, the option renders as a blank entry in the dropdown, which creates several problems.
From an accessibility standpoint, screen readers rely on the option’s label to announce each choice to the user. An empty, unlabeled option gives assistive technology nothing meaningful to read, making the control unusable for some users. From a usability perspective, sighted users see a blank line in the dropdown and have no idea what it represents. And from a standards compliance perspective, the HTML specification explicitly requires that an <option> without a label attribute must not have empty content.
Note that the value attribute is separate from the display text. The value is what gets submitted with the form, while the label/text content is what the user sees. Setting a value does not satisfy the requirement for visible text.
How to fix it
You have two options:
- Add text content inside the <option> element (the most common and recommended approach).
- Add a label attribute to the <option> element. When present, the label attribute takes precedence over the text content for display purposes in many browsers.
If you intend for an option to serve as a placeholder (e.g., “Choose one…”), make sure it still has visible text content.
Examples
❌ Empty option without a label
This triggers the validation error because the <option> elements have no text content and no label attribute:
<select name="size">
<option value=""></option>
<option value="s"></option>
<option value="m"></option>
<option value="l"></option>
</select>
✅ Fix: Add text content inside each option
<select name="size">
<option value="">Choose a size</option>
<option value="s">Small</option>
<option value="m">Medium</option>
<option value="l">Large</option>
</select>
✅ Fix: Use the label attribute
The label attribute provides the display text. The element content can then be empty or differ from the label:
<select name="size">
<option value="" label="Choose a size"></option>
<option value="s" label="Small"></option>
<option value="m" label="Medium"></option>
<option value="l" label="Large"></option>
</select>
✅ Mixing both approaches
You can use text content for some options and the label attribute for others. You can even use both on the same element — in that case, the label attribute typically takes precedence for display:
<select name="size">
<option value="">Choose a size</option>
<option value="s">Small</option>
<option value="m" label="Medium">Medium (M)</option>
<option value="l" label="Large">Large (L)</option>
</select>
❌ Common mistake: Assuming value counts as a label
This is still invalid because value is not displayed to the user:
<select name="color">
<option value="red"></option>
</select>
✅ Corrected
<select name="color">
<option value="red">Red</option>
</select>
In almost all cases, placing readable text directly inside the <option> tags is the simplest and most compatible approach. Reserve the label attribute for situations where you need the displayed text to differ from the element’s content.
The <picture> element is a container that lets you provide multiple image sources for different viewport sizes, resolutions, or format support. It works together with zero or more <source> elements and exactly one <img> element. The browser evaluates each <source> in order, selects the best match based on its media, type, or srcset attributes, and then displays the chosen image in the space occupied by the <img> element. If no <source> matches—or if the browser doesn’t support the <picture> element at all—the <img> element’s src attribute is used as the final fallback.
The <img> element isn’t optional; it’s structurally required by the HTML specification. Without it, the <picture> element is incomplete and invalid. This matters for several reasons:
- Rendering: Browsers rely on the <img> element to actually display the image. A <picture> element with only <source> children will render nothing in most browsers.
- Accessibility: The <img> element carries the alt attribute, which provides a text alternative for screen readers and other assistive technologies. Without it, there’s no accessible description of the image.
- Fallback support: Older browsers that don’t understand <picture> or <source> will ignore those elements entirely and fall back to the <img>. Without it, those users see nothing.
- Standards compliance: The WHATWG HTML specification explicitly states that the <picture> element’s content model requires one <img> element, optionally preceded by <source> elements and inter-element whitespace.
The <img> element should always be the last child inside <picture>, placed after all <source> elements.
Examples
Invalid: <picture> without an <img> element
This markup triggers the validation error because the required <img> child is missing:
<picture>
<source srcset="hero-large.webp" type="image/webp">
<source srcset="hero-large.jpg" type="image/jpeg">
</picture>
Fixed: Adding the required <img> element
Add an <img> as the last child with a src pointing to the default image and a descriptive alt attribute:
<picture>
<source srcset="hero-large.webp" type="image/webp">
<source srcset="hero-large.jpg" type="image/jpeg">
<img src="hero-large.jpg" alt="A panoramic view of the mountain landscape">
</picture>
Using <picture> for responsive images with media queries
When serving different image sizes based on viewport width, the <img> element provides the default (typically the smallest) image:
<picture>
<source srcset="banner-wide.jpg" media="(min-width: 1200px)">
<source srcset="banner-medium.jpg" media="(min-width: 600px)">
<img src="banner-small.jpg" alt="Promotional banner for summer sale">
</picture>
The browser checks each <source> in order. If the viewport is 1200 pixels or wider, banner-wide.jpg is used. If it’s between 600 and 1199 pixels, banner-medium.jpg is used. Otherwise, the <img> element’s src value of banner-small.jpg is displayed.
Using <picture> for format selection
You can also use <picture> to offer modern image formats with a fallback for browsers that don’t support them:
<picture>
<source srcset="photo.avif" type="image/avif">
<source srcset="photo.webp" type="image/webp">
<img src="photo.jpg" alt="Close-up of a sunflower in bloom">
</picture>
Browsers that support AVIF will use the first source, those that support WebP will use the second, and all others will fall back to the JPEG specified in the <img> element. In every case, the <img> element is what makes the <picture> element valid and functional.
The async attribute tells the browser to download and execute a script without blocking HTML parsing. For external scripts (those with a src attribute), this means the browser can continue parsing the page while fetching the file, then execute the script as soon as it’s available. For inline module scripts (type="module"), async changes how the module’s dependency graph is handled — the module and its imports execute as soon as they’re all ready, rather than waiting for HTML parsing to complete.
For a classic inline script (no src, no type="module"), there is nothing to download asynchronously. The browser encounters the code directly in the HTML and executes it immediately. Applying async in this context is meaningless and contradicts the HTML specification, which is why the W3C validator flags it as an error.
Beyond standards compliance, using async incorrectly can signal a misunderstanding of script loading behavior, which may lead to bugs. For example, a developer might mistakenly believe that async on an inline script will defer its execution, when in reality it has no effect and the script still runs synchronously during parsing.
How to Fix
You have several options depending on your intent:
- If the script should be external, move the code to a separate file and reference it with the src attribute alongside async.
- If the script should be an inline module, add type="module" to the <script> tag. Note that module scripts are deferred by default, and async makes them execute as soon as their dependencies are resolved rather than waiting for parsing to finish.
- If the script is a plain inline script, simply remove the async attribute — it has no practical effect anyway.
Examples
❌ Invalid: async on a classic inline script
<script async>
console.log("Hello, world!");
</script>
This triggers the validator error because there is no src attribute and the type is not "module".
✅ Fixed: Remove async from the inline script
<script>
console.log("Hello, world!");
</script>
✅ Fixed: Use async with an external script
<script async src="app.js"></script>
The async attribute is valid here because the browser needs to fetch app.js from the server, and async controls when that downloaded script executes relative to parsing.
✅ Fixed: Use async with an inline module
<script async type="module">
import { greet } from "./utils.js";
greet();
</script>
This is valid because module scripts have a dependency resolution phase that can happen asynchronously. The async attribute tells the browser to execute the module as soon as all its imports are resolved, without waiting for the document to finish parsing.
❌ Invalid: async with type="text/javascript" (not a module)
<script async type="text/javascript">
console.log("This is still invalid.");
</script>
Even though type is specified, only type="module" satisfies the requirement. The value "text/javascript" is the default classic script type and does not make async valid on an inline script.
The charset attribute on the <script> element tells the browser what character encoding to use when interpreting the referenced external script file. When a script is written directly inside the HTML document (an inline script), the script’s character encoding is inherently the same as the document’s encoding — there is no separate file to decode. Because of this, the HTML specification requires that charset only appear on <script> elements that also have a src attribute pointing to an external file.
Including charset without src violates the HTML specification and signals a misunderstanding of how character encoding works for inline scripts. Validators flag this because browsers ignore the charset attribute on inline scripts, which means it has no effect and could mislead developers into thinking they’ve set the encoding when they haven’t.
It’s also worth noting that the charset attribute on <script> is deprecated in the HTML living standard, even for external scripts. The modern best practice is to serve external script files with the correct Content-Type HTTP header (e.g., Content-Type: application/javascript; charset=utf-8) or to simply ensure all your files use UTF-8 encoding, which is the default. If you’re working with an older codebase that still uses charset, consider removing it entirely and relying on UTF-8 throughout.
Examples
Incorrect: charset on an inline script
This triggers the validation error because charset is specified without a corresponding src attribute.
<script charset="utf-8">
console.log("Hello, world!");
</script>
Correct: Remove charset from inline scripts
Since inline scripts use the document’s encoding, simply remove the charset attribute.
<script>
console.log("Hello, world!");
</script>
Correct: Use charset with an external script (deprecated but valid)
If you need to specify the encoding of an external script, both charset and src must be present. Note that this usage, while valid, is deprecated.
<script src="app.js" charset="utf-8"></script>
Recommended: External script without charset
The preferred modern approach is to omit charset entirely and ensure the server delivers the file with the correct encoding header, or simply use UTF-8 for everything.
<script src="app.js"></script>
The defer and async boolean attributes control how and when an external script is fetched and executed relative to HTML parsing. These attributes exist specifically to optimize the loading of external resources. An inline <script> block (one without a src attribute) doesn’t need to be “downloaded” — its content is already embedded in the HTML document. Because of this, the defer attribute has no meaningful effect on inline scripts, and the HTML specification explicitly forbids this combination.
According to the WHATWG HTML living standard, the defer attribute “must not be specified if the src attribute is not present.” Browsers will simply ignore the defer attribute on inline scripts, which means the script will execute synchronously as if defer were never added. This can mislead developers into thinking their inline script execution is being deferred when it isn’t, potentially causing subtle timing bugs that are difficult to diagnose.
The same rule applies to the async attribute — it also requires the presence of a src attribute to be valid.
How to fix it
You have two options depending on your situation:
- If the script should be deferred, move the inline code into an external .js file and reference it with the src attribute alongside defer.
- If the script must remain inline, remove the defer attribute entirely. If you need deferred execution for inline code, consider placing the <script> element at the end of the <body>, or use DOMContentLoaded to wait for the document to finish parsing.
Examples
❌ Invalid: defer on an inline script
<script defer>
console.log("hello");
</script>
This triggers the validation error because defer is present without a corresponding src attribute.
✅ Fix option 1: Add a src attribute
Move the JavaScript into an external file (e.g., app.js) and reference it:
<script defer src="app.js"></script>
The browser will download app.js in parallel with HTML parsing and execute it only after the document is fully parsed.
✅ Fix option 2: Remove defer from the inline script
If the script must stay inline, simply remove the defer attribute:
<script>
console.log("hello");
</script>
✅ Fix option 3: Use DOMContentLoaded for deferred inline execution
If you need your inline script to wait until the DOM is ready, wrap the code in a DOMContentLoaded event listener:
<script>
document.addEventListener("DOMContentLoaded", function() {
console.log("DOM is fully parsed");
});
</script>
This achieves a similar effect to defer but is valid for inline scripts.
❌ Invalid: async on an inline script
The same rule applies to async:
<script async>
document.title = "Updated";
</script>
✅ Fixed
<script async src="update-title.js"></script>
Or simply remove async if the script is inline:
<script>
document.title = "Updated";
</script>
The span element is a generic inline container with no inherent semantics. On its own, it carries no meaning for assistive technologies. When you add ARIA attributes like aria-expanded or aria-valuenow to a span, you are signaling that the element represents an interactive widget — but the validator (and assistive technologies) need more context. Many ARIA attributes are only permitted on elements that have certain roles, and some roles require a specific set of attributes to function correctly.
For example, aria-valuenow is designed for range widgets like sliders and progress bars. According to the WAI-ARIA specification, if you use aria-valuenow, the element must also have aria-valuemin, aria-valuemax, and a role such as progressbar, slider, meter, or scrollbar. Similarly, aria-expanded is meant for elements with roles like button, combobox, link, or treeitem. Placing these attributes on a bare span without the corresponding role violates the ARIA rules and triggers this validation error.
This matters for several reasons:
- Accessibility: Screen readers rely on the role to determine how to present a widget to users. Without it, ARIA state attributes become meaningless or confusing.
- Standards compliance: The HTML specification integrates ARIA rules, and validators enforce that ARIA attributes are used in valid combinations.
- Browser behavior: Browsers use the role to build the accessibility tree. A span with aria-valuenow but no role may be ignored or misrepresented to assistive technology users.
How to fix it
- Add the correct role to the span, along with all attributes required by that role.
- Use a semantic HTML element instead of a span when one exists (e.g., <progress> or <button>).
- Remove unnecessary ARIA attributes if the span is purely decorative or the attributes were added by mistake.
If your span is purely visual (e.g., a decorative asterisk for required fields), don’t add state-related ARIA attributes to it. Instead, use aria-hidden="true" to hide it from assistive technologies, and place ARIA attributes on the actual form control.
Examples
Incorrect: aria-expanded on a span without a role
<span aria-expanded="false">Menu</span>
The validator reports the missing role because aria-expanded isn’t valid on a generic span.
Correct: Add a role (or use a button)
<span role="button" tabindex="0" aria-expanded="false">Menu</span>
Or, better yet, use a real button element:
<button aria-expanded="false">Menu</button>
Incorrect: aria-valuenow without the full set of range attributes and role
<span class="progress-indicator" aria-valuenow="50">50%</span>
Correct: Include the role and all required range attributes
<span role="progressbar" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100">
50%
</span>
Or use the native <progress> element, which has built-in semantics:
<progress value="50" max="100">50%</progress>
Incorrect: aria-required on a decorative span
<label for="email">
Email
<span class="required" aria-required="true">*</span>
</label>
<input id="email" name="email" type="email">
The aria-required attribute belongs on the form control, not on the decorative asterisk.
Correct: Hide the decorative indicator and mark the input as required
<label for="email">
Email
<span class="required" aria-hidden="true">*</span>
</label>
<input id="email" name="email" type="email" aria-required="true">
If you also want screen readers to announce “required” as part of the label text, add visually hidden text:
<label for="email">
Email
<span aria-hidden="true">*</span>
<span class="visually-hidden">required</span>
</label>
<input id="email" name="email" type="email" required>
The key takeaway: whenever you use ARIA state or property attributes on a span, make sure the element also has the correct role and all companion attributes required by that role. When a native HTML element already provides the semantics you need — such as <button>, <progress>, or <meter> — prefer it over a span with ARIA, as native elements are more robust and require less additional markup.
The aria-checked attribute communicates the checked state of an interactive widget to assistive technologies. According to the WAI-ARIA specification, this attribute is only permitted on elements that have a role supporting the “checked” state — such as checkbox, switch, radio, menuitemcheckbox, or menuitemradio. A plain <td> element has an implicit role of cell (or gridcell when inside a role="grid" table), neither of which supports aria-checked. When the validator encounters aria-checked on a <td> without a compatible role, it flags the element as invalid.
This matters for several reasons:
- Accessibility: Screen readers and other assistive technologies rely on the relationship between role and ARIA state attributes. An aria-checked on an element without a recognized checkable role creates a confusing or broken experience — users may not understand that the cell is supposed to be interactive.
- Standards compliance: The ARIA in HTML specification defines strict rules about which attributes are allowed on which roles. Violating these rules means your HTML is technically invalid.
- Browser behavior: Browsers may ignore aria-checked entirely when it’s used on an element without a valid role, making the attribute useless.
How to fix it
You have two main approaches depending on what your <td> is meant to do:
1. Add an appropriate role attribute. If the table cell genuinely represents a checkable control (for example, in an interactive data grid), add role="checkbox", role="switch", or another appropriate checkable role to the <td>, along with tabindex for keyboard accessibility.
2. Remove aria-checked and use a real control. If the cell simply contains a checkbox or toggle, place an actual <input type="checkbox"> inside the <td> and remove the ARIA attributes from the cell itself. Native HTML controls already communicate their state to assistive technologies without extra ARIA.
Examples
❌ Incorrect: aria-checked without a role
<table>
<tr>
<td aria-checked="true">Selected</td>
<td>Item A</td>
</tr>
</table>
This triggers the error because <td> has the implicit role of cell, which does not support aria-checked.
✅ Fix: Add a compatible role to the <td>
<table role="grid">
<tr>
<td role="checkbox" aria-checked="true" tabindex="0">Selected</td>
<td>Item A</td>
</tr>
</table>
Here the <td> explicitly has role="checkbox", which supports aria-checked. The tabindex="0" makes it keyboard-focusable, and role="grid" on the table signals that cells may be interactive.
✅ Fix: Use a native checkbox inside the <td>
<table>
<tr>
<td>
<label>
<input type="checkbox" checked>
Selected
</label>
</td>
<td>Item A</td>
</tr>
</table>
This approach is often the best option. The native <input type="checkbox"> already conveys its checked state to assistive technologies, and no ARIA attributes are needed on the <td>.
❌ Incorrect: Mismatched role and aria-checked
<table>
<tr>
<td role="button" aria-checked="false">Toggle</td>
<td>Item B</td>
</tr>
</table>
The button role does not support aria-checked. This would trigger a different but related validation error.
✅ Fix: Use a role that supports aria-checked
<table role="grid">
<tr>
<td role="switch" aria-checked="false" tabindex="0">Toggle</td>
<td>Item B</td>
</tr>
</table>
The switch role supports aria-checked and is appropriate for toggle-style controls.
The HTML specification requires every document to have a <title> element with at least one non-whitespace character. The title serves as the primary label for the page — it appears in the browser tab, in bookmarks, in search engine results, and is announced by screen readers when a user navigates to the page. An empty title leaves users with no way to identify or distinguish the page, which is especially problematic for accessibility. Screen reader users rely on the document title to understand what page they’ve landed on, and an empty title provides no context at all.
Browsers may attempt to display something (such as the URL) when the title is missing or empty, but this fallback is inconsistent and often produces a poor user experience. Search engines also depend on the <title> element for indexing and displaying results, so an empty title can negatively affect discoverability.
This error commonly occurs when templates or boilerplate code include a <title> tag as a placeholder that never gets filled in, or when content management systems fail to inject a title value into the template.
How to fix it
Add descriptive, concise text inside the <title> element that accurately reflects the content of the page. A good title is typically 20–70 characters long and specific enough to distinguish the page from other pages on the same site.
- Every page should have a unique title relevant to its content.
- Avoid generic titles like “Untitled” or “Page” — be specific.
- For multi-page sites, consider a format like “Page Name - Site Name”.
Examples
❌ Empty title element
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<p>Welcome to our website.</p>
</body>
</html>
This triggers the error because the <title> element contains no text.
❌ Title with only whitespace
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title> </title>
</head>
<body>
<p>Welcome to our website.</p>
</body>
</html>
This also triggers the error. Whitespace-only content is treated as empty.
✅ Title with descriptive text
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Getting Started - Automated Website Validator</title>
</head>
<body>
<p>Welcome to our website.</p>
</body>
</html>
The <title> element now contains meaningful text that identifies both the page and the site, making it accessible, SEO-friendly, and standards-compliant.
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