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 th element already carries semantic meaning as a table header cell. Nesting a heading element like h4 inside it creates a conflict in the document’s outline and semantic structure. Screen readers and other assistive technologies treat headings and table headers as distinct navigational landmarks, so combining them can confuse users who rely on these tools to understand page structure. A heading buried inside a table cell may break the expected heading hierarchy, making it harder for users to navigate by headings.
According to the HTML specification, the content model of th is “flow content, but with no header, footer, sectioning content, or heading content descendants.” This means h1, h2, h3, h4, h5, and h6 are all explicitly disallowed inside th.
The reason developers often place headings inside th is to achieve a specific visual style — larger, bolder text. But th elements are already rendered bold by default in most browsers, and any additional styling should be handled with CSS rather than repurposing heading elements.
How to Fix It
- Remove the heading element from inside the th and use the text directly.
- Style with CSS if you need the th content to look different from default styling.
- Move the heading outside the table if it serves as a title or caption for the table. Consider using the <caption> element for table titles.
Examples
❌ Incorrect: Heading inside th
<table>
<tr>
<th><h4>Product</h4></th>
<th><h4>Price</h4></th>
</tr>
<tr>
<td>Widget</td>
<td>$25</td>
</tr>
</table>
This triggers the validation error because h4 elements are not allowed as descendants of th.
✅ Fixed: Plain text in th
<table>
<tr>
<th>Product</th>
<th>Price</th>
</tr>
<tr>
<td>Widget</td>
<td>$25</td>
</tr>
</table>
The simplest fix — just remove the heading tags. The th element already conveys that these cells are headers.
✅ Fixed: Using CSS for custom styling
If you need the header cells to have a specific visual appearance, use CSS:
<table>
<tr>
<th class="styled-header">Product</th>
<th class="styled-header">Price</th>
</tr>
<tr>
<td>Widget</td>
<td>$25</td>
</tr>
</table>
<style>
.styled-header {
font-size: 1.2em;
text-transform: uppercase;
}
</style>
✅ Fixed: Moving the heading outside and using caption
If the heading was meant to serve as a title for the table, use the <caption> element instead:
<table>
<caption>Product Pricing</caption>
<tr>
<th>Product</th>
<th>Price</th>
</tr>
<tr>
<td>Widget</td>
<td>$25</td>
</tr>
</table>
The <caption> element is purpose-built for labeling tables and is well-supported by assistive technologies. You can also place a heading before the table if it fits your document’s heading hierarchy:
<h4>Product Pricing</h4>
<table>
<tr>
<th>Product</th>
<th>Price</th>
</tr>
<tr>
<td>Widget</td>
<td>$25</td>
</tr>
</table>
Both approaches keep your HTML valid while preserving clear semantics for both visual users and assistive technology.
The <iframe> element embeds an entirely separate HTML document within the current page, creating its own independent browsing context. The <a> element, on the other hand, is an interactive element designed to navigate users to a new URL or location. When you nest an <iframe> inside an <a>, browsers face a conflict: user interactions like clicks could be intended for the embedded content inside the iframe or for the link itself. The HTML specification resolves this ambiguity by simply disallowing it.
According to the WHATWG HTML living standard, the <a> element’s content model does not permit interactive content as descendants. The <iframe> element is categorized as interactive content, which means it must not appear anywhere inside an <a> tag — not as a direct child and not nested deeper within other elements inside the link.
This restriction matters for several reasons:
- Accessibility: Screen readers and assistive technologies cannot reliably convey the purpose of a link that contains an embedded document. Users may not understand whether they are interacting with the link or the iframe.
- Unpredictable behavior: Different browsers may handle clicks on the iframe-inside-a-link differently, leading to inconsistent user experiences.
- Standards compliance: Violating the content model makes your HTML invalid, which can cause unexpected rendering and behavior.
To fix the issue, restructure your markup so the <iframe> and <a> are siblings or otherwise separated. If your goal is to provide a link alongside embedded content, place the link before or after the iframe. If you want a clickable preview that links somewhere, consider using an image thumbnail inside the link instead of an iframe.
Examples
❌ Invalid: <iframe> inside an <a> element
<a href="https://example.com">
<iframe src="https://example.com/embed"></iframe>
</a>
This triggers the validation error because the <iframe> is a descendant of the <a> element.
❌ Invalid: <iframe> nested deeper inside an <a> element
<a href="https://example.com">
<div>
<iframe src="https://example.com/embed"></iframe>
</div>
</a>
Even though the <iframe> is not a direct child, it is still a descendant of the <a> element, which is not allowed.
✅ Valid: Separate the <iframe> and <a> elements
<a href="https://example.com">Visit Example.com</a>
<iframe src="https://example.com/embed"></iframe>
The link and the iframe are siblings, so there is no nesting conflict.
✅ Valid: Use an image as a clickable preview instead
If the intent is to create a clickable preview that links to a page, use a thumbnail image rather than an iframe:
<a href="https://example.com">
<img src="preview-thumbnail.jpg" alt="Preview of Example.com">
</a>
✅ Valid: Wrap in a container with a separate link
If you need both an iframe and a related link displayed together, use a wrapper element:
<div class="embed-container">
<iframe src="https://example.com/embed" title="Embedded content from Example.com"></iframe>
<p><a href="https://example.com">Open Example.com in a new page</a></p>
</div>
Note that when using <iframe>, it’s good practice to include a title attribute to describe the embedded content for accessibility purposes.
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.
In HTML5, the only namespace-related attribute permitted on the <html> element is xmlns with the value http://www.w3.org/1999/xhtml, and even that is optional—it exists solely for compatibility with XHTML serialization. Prefixed namespace declarations like xmlns:w, xmlns:o, xmlns:v, and similar attributes are XML features that have no meaning in the HTML syntax. They originate from Microsoft Office’s HTML export, which generates markup containing proprietary XML namespaces such as urn:schemas-microsoft-com:office:word for Word-specific elements and styling.
Why This Is a Problem
Standards compliance: The HTML living standard (WHATWG) explicitly does not support custom namespace declarations. Including them makes your document non-conforming, and the W3C validator will report an error for each one.
No browser benefit: Modern browsers parsing HTML5 ignore these namespace declarations entirely. The attributes serve no functional purpose on the web—they only add unnecessary bloat to your markup.
Maintenance and readability: Office-generated HTML is notoriously verbose. Leaving namespace declarations in place often goes hand-in-hand with other Office artifacts (conditional comments, <o:p> tags, mso- style properties) that clutter your code and make it harder to maintain.
Accessibility and interoperability: While browsers typically tolerate these extra attributes without visible issues, non-browser HTML consumers—such as screen readers, search engine crawlers, or email clients—may handle unexpected attributes unpredictably.
How to Fix It
- Remove the xmlns:w attribute from your <html> tag or any other element where it appears.
- Remove related namespace declarations like xmlns:o (Office), xmlns:v (VML), and xmlns:m (Math) if present.
- Clean up Office-specific elements such as <w:Sdt>, <o:p>, or <v:shape> that depend on those namespaces—these are not valid HTML elements.
- Strip Office-specific CSS properties prefixed with mso- (e.g., mso-bidi-font-family) that often accompany namespace declarations.
If you regularly paste content from Microsoft Word, consider using a “paste as plain text” feature in your editor, or use an HTML cleaning tool to strip Office artifacts automatically.
Examples
Invalid: Office namespace on the <html> element
<!DOCTYPE html>
<html lang="en" xmlns:w="urn:schemas-microsoft-com:office:word">
<head>
<title>Document</title>
</head>
<body>
<p>Content from Word</p>
</body>
</html>
Invalid: Multiple Office namespaces
<!DOCTYPE html>
<html lang="en"
xmlns:w="urn:schemas-microsoft-com:office:word"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<title>Document</title>
</head>
<body>
<p class="MsoNormal">Content from Word<o:p></o:p></p>
</body>
</html>
Valid: Clean HTML without namespace declarations
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<p>Content from Word</p>
</body>
</html>
Valid: Using the standard xmlns attribute (optional)
If you need XHTML compatibility, the standard xmlns attribute (without a prefix) is permitted:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Document</title>
</head>
<body>
<p>Content from Word</p>
</body>
</html>
Note that this same validation error applies to any custom prefixed namespace attribute—not just xmlns:w. If you see similar errors for xmlns:o, xmlns:v, xmlns:st1, or others, the fix is the same: remove them along with any elements or attributes that depend on those namespaces.
The HTML5 specification strictly limits which attributes are valid on the <html> element—and on elements in general. Standard global attributes like lang, dir, class, and id are permitted, and in XHTML serialization the plain xmlns attribute is allowed to declare the default XHTML namespace. However, prefixed namespace declarations like xmlns:m, xmlns:o, xmlns:v, or xmlns:w are XML constructs that have no meaning in the HTML5 (text/html) parsing model.
These prefixed namespace attributes most often appear when content is generated by or copied from Microsoft Office products (Word, Excel, PowerPoint). Office uses custom XML namespaces such as xmlns:m for Office MathML (http://schemas.microsoft.com/office/2004/12/omml) and xmlns:o for Office-specific markup. When this markup is saved as HTML or pasted into a web page, these declarations come along and trigger validation errors.
Why This Is a Problem
- Standards compliance: The W3C HTML5 specification does not support custom XML namespace declarations in the text/html serialization. Validators will flag every such attribute.
- No functional benefit: HTML5 parsers ignore namespace prefixes entirely. The xmlns:m declaration does nothing in a browser rendering an HTML5 page, so it is dead code.
- Content bloat: Office-generated HTML often includes many unnecessary namespace declarations, inline styles, and proprietary elements that bloat the document and make it harder to maintain.
- Accessibility and interoperability: Clean, valid HTML is easier for assistive technologies, search engines, and other user agents to process reliably.
How to Fix It
- Remove the custom namespace attribute from the <html> element (or whichever element it appears on).
- Remove any elements or attributes that depend on that namespace prefix (e.g., <m:oMath>, <o:p>), since they are not valid HTML5 elements.
- Replace with HTML5-native equivalents where possible. For example, MathML is natively supported in HTML5 without any namespace declaration—you can use <math> elements directly.
- If you truly need XML namespaces, serve your document as XHTML with the application/xhtml+xml content type instead of text/html.
Examples
Incorrect: Custom namespace on the html element
This triggers the “Attribute xmlns:m not allowed here” error:
<!DOCTYPE html>
<html xmlns:m="http://schemas.microsoft.com/office/2004/12/omml"
xmlns:o="urn:schemas-microsoft-com:office:office"
lang="en">
<head>
<title>Office Paste Example</title>
</head>
<body>
<p>Some content with Office markup.</p>
</body>
</html>
Correct: Clean HTML5 without namespace declarations
<!DOCTYPE html>
<html lang="en">
<head>
<title>Clean HTML5 Example</title>
</head>
<body>
<p>Some content without Office markup.</p>
</body>
</html>
Correct: Using MathML natively in HTML5
If the xmlns:m namespace was being used for mathematical content, HTML5 supports MathML directly without any namespace declaration:
<!DOCTYPE html>
<html lang="en">
<head>
<title>MathML in HTML5</title>
</head>
<body>
<p>The quadratic formula:</p>
<math>
<mi>x</mi>
<mo>=</mo>
<mfrac>
<mrow>
<mo>-</mo>
<mi>b</mi>
<mo>±</mo>
<msqrt>
<mrow>
<msup><mi>b</mi><mn>2</mn></msup>
<mo>-</mo>
<mn>4</mn><mi>a</mi><mi>c</mi>
</mrow>
</msqrt>
</mrow>
<mrow>
<mn>2</mn><mi>a</mi>
</mrow>
</mfrac>
</math>
</body>
</html>
Incorrect: Namespace attribute on a non-html element
The same error can appear on other elements too:
<div xmlns:o="urn:schemas-microsoft-com:office:office">
<p>Office content</p>
</div>
Correct: Remove the namespace attribute
<div>
<p>Office content</p>
</div>
If you’re cleaning up Office-generated HTML, consider using a dedicated tool or a “paste as plain text” option in your CMS to strip out proprietary markup before it enters your pages. This keeps your HTML lean, valid, and maintainable.
In HTML5, SVG elements are natively supported and don’t require explicit namespace declarations at all. When you write an <svg> tag inside an HTML document, the browser automatically associates it with the correct SVG namespace (http://www.w3.org/2000/svg). The xmlns:svg="http://www.w3.org/2000/svg" attribute is a prefixed namespace binding — it declares svg as a namespace prefix — which is a concept from XML/XHTML workflows that HTML5’s parser does not support or recognize.
The HTML specification defines a limited set of allowed attributes on each element. Since xmlns:svg is not among them for the <svg> element (or any HTML5 element), the W3C validator flags it as invalid. The only namespace-related attribute the HTML parser tolerates on <svg> is xmlns="http://www.w3.org/2000/svg", and even that is optional — it’s accepted purely for compatibility with XHTML but has no functional effect in HTML5.
This issue commonly appears when SVG code is exported from graphic editors like Inkscape or Adobe Illustrator, or when SVG files originally written as standalone XML documents are pasted directly into HTML. These tools sometimes include verbose namespace declarations that are necessary in XML contexts but invalid in HTML.
Why it matters
- Standards compliance: The HTML5 specification explicitly does not allow prefixed namespace attributes. Using them results in validation errors.
- Code cleanliness: Unnecessary attributes add clutter without any benefit, making your markup harder to read and maintain.
- Potential parsing issues: While most browsers silently ignore unrecognized attributes, relying on lenient browser behavior rather than valid markup can lead to unexpected results in edge cases or non-browser HTML consumers (e.g., email clients, content scrapers, or accessibility tools).
How to fix it
- Locate all <svg> elements in your HTML that contain the xmlns:svg attribute.
- Remove the xmlns:svg="http://www.w3.org/2000/svg" attribute entirely.
- If you’re working in an HTML5 document (not XHTML), the xmlns="http://www.w3.org/2000/svg" attribute is also optional — you can keep or remove it.
- If your SVG uses other prefixed namespace declarations like xmlns:xlink, consider replacing them with their HTML5 equivalents (e.g., use href instead of xlink:href).
Examples
Incorrect: using xmlns:svg on an SVG element
The xmlns:svg attribute triggers the validation error:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="blue" />
</svg>
Correct: removing the prefixed namespace
Remove xmlns:svg and keep only the standard attributes:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="blue" />
</svg>
Also correct: minimal inline SVG in HTML5
In HTML5, the xmlns attribute itself is optional for inline SVG, so this is the cleanest approach:
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="blue" />
</svg>
Cleaning up multiple unnecessary namespaces
SVG exported from editors often includes several unnecessary namespace declarations. Here’s a before-and-after of a typical cleanup:
Before (multiple invalid namespace attributes):
<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 200 200">
<image xlink:href="photo.jpg" width="200" height="200" />
</svg>
After (cleaned up for HTML5):
<svg viewBox="0 0 200 200">
<image href="photo.jpg" width="200" height="200" />
</svg>
Note that xlink:href has been replaced with href, which is the modern standard supported by all current browsers. The xmlns:xlink declaration is no longer needed either.
The xmlns:dt attribute — short for “XML Namespace: datatypes” — was historically used in Microsoft-specific XML vocabularies (notably urn:schemas-microsoft-com:datatypes) to declare data type information on elements. It was common in older ASP and IE-era markup. However, HTML5 is not an XML language, and it does not support arbitrary XML namespace declarations on elements.
In HTML5, the only xmlns attribute permitted is xmlns="http://www.w3.org/1999/xhtml" on the <html> element itself, and even that exists solely for compatibility with XHTML serialization. Namespace-prefixed attributes like xmlns:dt, xmlns:o, or xmlns:v are invalid. The HTML parser simply does not recognize them, and the W3C validator will flag them with the error “Attribute ‘xmlns:dt’ not allowed here.”
Why This Is a Problem
- Standards compliance: Using non-standard attributes means your document does not conform to the HTML specification, which can lead to unpredictable behavior across browsers.
- Legacy lock-in: The xmlns:dt attribute is tied to Microsoft’s proprietary data type schema. Modern browsers do not interpret or use this namespace, so it serves no functional purpose in an HTML5 document.
- Validation noise: Invalid attributes generate validator errors that can obscure real issues in your markup, making it harder to catch genuine bugs.
- Accessibility and tooling: Screen readers, search engine crawlers, and other automated tools expect valid HTML. Non-standard attributes can confuse parsers or be silently discarded.
How to Fix It
- Remove the attribute. If xmlns:dt was carried over from legacy code or a CMS template and nothing in your application depends on it, simply delete it.
- Replace with data-* attributes. If you need to attach custom metadata to an element — for example, to indicate a data type for use by JavaScript — use an HTML5 data-* attribute instead.
- Use XHTML if XML namespaces are required. If you genuinely need XML namespace support (rare in modern web development), serve your document as application/xhtml+xml with a proper XHTML doctype and XML declaration. Be aware this changes parsing rules significantly.
Examples
Incorrect: Using xmlns:dt in HTML5
This will trigger the validation error:
<ul xmlns:dt="urn:schemas-microsoft-com:datatypes">
<li dt:dt="string">Item one</li>
<li dt:dt="number">42</li>
</ul>
Both xmlns:dt on the <ul> and the dt:dt attributes on the <li> elements are invalid in HTML5.
Correct: Attribute removed
If the namespace declaration is unnecessary (which it almost always is in modern HTML), remove it along with any prefixed attributes:
<ul>
<li>Item one</li>
<li>42</li>
</ul>
Correct: Using data-* attributes for custom metadata
If your JavaScript or application logic needs to know the data type of each item, use valid data-* attributes:
<ul>
<li data-type="string">Item one</li>
<li data-type="number">42</li>
</ul>
You can then access these values in JavaScript with element.dataset.type.
Correct: Migrating a legacy div wrapper
A common legacy pattern places xmlns:dt on a container div:
<!-- Invalid -->
<div xmlns:dt="urn:schemas-microsoft-com:datatypes">
<span dt:dt="dateTime">2024-01-15</span>
</div>
The fixed version removes the namespace and uses standard attributes:
<div>
<time datetime="2024-01-15">January 15, 2024</time>
</div>
In this case, the semantic <time> element with its datetime attribute is the proper HTML5 way to represent date and time values — no custom namespace needed.
Quick Checklist
- Search your codebase for xmlns:dt and any dt: prefixed attributes.
- Check CMS templates, server-generated markup, and copy-pasted legacy code — these are the most common sources.
- Remove the attributes or replace them with data-* equivalents.
- Re-validate your document to confirm the error is resolved.
The xmlns:fb attribute was an XML namespace declaration used to enable FBML (Facebook Markup Language) tags on websites. FBML allowed developers to embed Facebook-specific elements like <fb:like>, <fb:comments>, and <fb:share-button> directly in their HTML. Facebook officially retired FBML in 2011 and replaced it with the JavaScript SDK and social plugins, yet many websites still carry this outdated namespace declaration in their markup.
In HTML5, the only xmlns attribute allowed on the <html> element is the standard xmlns="http://www.w3.org/1999/xhtml", and even that is optional. Custom namespace prefixes like xmlns:fb or xmlns:og are XML constructs that have no meaning in HTML5 and are flagged as validation errors.
Why This Is a Problem
- Standards compliance: HTML5 does not support arbitrary XML namespace declarations. The xmlns:fb attribute violates the HTML specification, producing a validation error.
- Dead technology: FBML no longer functions. Facebook’s servers no longer process FBML tags, so the namespace serves no purpose whatsoever.
- Code cleanliness: Keeping deprecated, non-functional attributes in your markup adds confusion for developers maintaining the codebase and suggests the site hasn’t been updated in a long time.
How to Fix It
- Remove xmlns:fb (and any other custom namespace like xmlns:og) from your <html> tag.
- Remove any FBML tags such as <fb:like>, <fb:comments>, or <fb:share-button> from your page content.
-
Replace with modern alternatives:
- Use the Facebook JavaScript SDK for social plugins.
- Use Open Graph <meta> tags in the <head> to control how your pages appear when shared on Facebook. These <meta> tags use the property attribute (e.g., property="og:title") and do not require any namespace declaration in HTML5.
Examples
❌ Invalid: Using xmlns:fb on the <html> tag
<!DOCTYPE html>
<html lang="en" xmlns:fb="http://ogp.me/ns/fb#">
<head>
<title>My Page</title>
</head>
<body>
<fb:like href="https://example.com" width="300"></fb:like>
</body>
</html>
This triggers the error “Attribute ‘xmlns:fb’ not allowed here” because HTML5 does not permit custom XML namespace prefixes on the <html> element. The <fb:like> tag is also invalid HTML and no longer functional.
✅ Valid: Using Open Graph meta tags and the JavaScript SDK
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
<meta property="og:title" content="My Page">
<meta property="og:description" content="A description of my page.">
<meta property="og:url" content="https://example.com">
<meta property="og:image" content="https://example.com/image.jpg">
</head>
<body>
<h1>My Page</h1>
<!-- Facebook Like button using the JavaScript SDK -->
<div class="fb-like" data-href="https://example.com" data-width="300" data-layout="button_count"></div>
<script async defer crossorigin="anonymous"
src="https://connect.facebook.net/en_US/sdk.js#xfbml=1&version=v18.0">
</script>
</body>
</html>
The xmlns:fb attribute is gone, Open Graph metadata is provided via standard <meta> tags, and the like button is rendered using Facebook’s JavaScript SDK with data-* attributes—all fully valid HTML5.
In HTML5, the only namespace attributes recognized on SVG elements are xmlns (for the SVG namespace) and xmlns:xlink (for XLink, though xlink is now largely deprecated in favor of plain attributes). Any other custom namespace declarations — such as xmlns:serif, xmlns:inkscape, or xmlns:sketch — are not permitted by the HTML specification and will trigger a validation error.
Design tools like Affinity Designer, Adobe Illustrator, Inkscape, and Sketch often embed proprietary namespace declarations and metadata attributes in exported SVG files. These serve as internal markers for the design application (for example, to preserve layer names or editor-specific settings) but have no meaning in a web browser. Browsers simply ignore them, so they add unnecessary bytes to your page without providing any benefit.
Removing these attributes is important for several reasons:
- Standards compliance: The HTML5 parser has a fixed list of allowed namespace declarations. Custom ones violate the spec.
- Clean markup: Proprietary metadata bloats your SVG files with information that only matters inside the originating design tool.
- Maintainability: Removing tool-specific artifacts makes your SVGs easier to read, edit, and optimize.
To fix this, open your SVG file (or the HTML file containing inline SVGs) and remove the xmlns:serif attribute from the <svg> element. You should also search for and remove any attributes prefixed with serif: (such as serif:id) throughout the SVG, since those depend on the now-removed namespace declaration.
For a more automated approach, consider using SVGO or similar SVG optimization tools, which strip out editor metadata and unnecessary namespace declarations by default.
Examples
Incorrect — custom namespace attribute present
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:serif="https://www.serif.com/"
viewBox="0 0 100 100">
<circle serif:id="MainCircle" cx="50" cy="50" r="40" fill="blue" />
</svg>
This triggers two types of errors: xmlns:serif is not allowed on the <svg> element, and serif:id is not a recognized attribute on <circle>.
Correct — custom namespace and prefixed attributes removed
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="blue" />
</svg>
Incorrect — multiple proprietary namespaces
Design tools sometimes add several custom namespaces at once:
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:serif="https://www.serif.com/"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 200 200">
<g serif:id="Layer1">
<rect x="10" y="10" width="80" height="80" fill="red" />
</g>
</svg>
Correct — only standard namespaces retained
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 200 200">
<g>
<rect x="10" y="10" width="80" height="80" fill="red" />
</g>
</svg>
Note that xmlns:xlink was also removed in this example. While it won’t always trigger a validation error by itself, it’s unnecessary if no xlink:href attributes are used — and modern HTML5 SVG usage favors plain href over xlink:href anyway.
The xmlns:v attribute declares the XML namespace urn:schemas-microsoft-com:vml, which enabled Microsoft’s proprietary Vector Markup Language in Internet Explorer versions 5 through 8. VML was a way to render vector shapes directly in HTML before SVG gained broad browser support. When working with XML-based documents (like XHTML), namespace declarations such as xmlns:v were syntactically valid. However, HTML5 is not an XML language — it has its own parsing rules and only recognizes a limited set of namespace declarations on the <html> element, specifically xmlns (for the default XHTML namespace) and xmlns:xlink (in certain SVG/MathML contexts). Any other xmlns:* attribute, including xmlns:v, xmlns:o, and xmlns:w, triggers a validation error.
Why This Is a Problem
Standards compliance: The HTML5 specification explicitly does not allow arbitrary XML namespace declarations. The W3C validator flags xmlns:v because it is not part of the HTML5 attribute set for any element.
No modern browser support: VML was only supported in Internet Explorer, which has been discontinued. No current browser renders VML content, so the namespace declaration serves no purpose.
Code cleanliness: Keeping legacy, non-functional attributes clutters your markup and can confuse developers who maintain the code. It may also cause issues with HTML parsers, linters, and build tools that enforce strict HTML5 compliance.
How to Fix It
- Remove the xmlns:v attribute from your <html> tag (or wherever it appears).
- Remove any related namespace attributes like xmlns:o (Office namespace) or xmlns:w (Word namespace), which are also invalid in HTML5 and often appear alongside xmlns:v.
- Replace VML content with SVG if your page relied on VML for vector graphics. SVG is natively supported in all modern browsers and requires no namespace declaration on the <html> element.
- Ensure the lang attribute is set on the <html> element for accessibility, since you’re already editing that line.
Examples
Invalid: Using xmlns:v on the html element
<!DOCTYPE html>
<html xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:w="urn:schemas-microsoft-com:office:word">
<head>
<title>My Page</title>
</head>
<body>
<p>Hello world</p>
</body>
</html>
This triggers multiple validation errors — one for each xmlns:* attribute that HTML5 does not recognize.
Valid: Namespace attributes removed
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Page</title>
</head>
<body>
<p>Hello world</p>
</body>
</html>
Replacing VML with SVG
If your page used VML to draw shapes, replace the VML markup with inline SVG. No namespace declaration on <html> is needed — the <svg> element carries its own namespace implicitly in HTML5.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Vector Graphics with SVG</title>
</head>
<body>
<svg width="200" height="200" aria-label="Blue circle">
<circle cx="100" cy="100" r="80" fill="steelblue" />
</svg>
</body>
</html>
Common Sources of This Issue
This attribute frequently appears in HTML that was:
- Exported from Microsoft Word or Outlook. These applications generate HTML with VML namespace declarations for shapes, text effects, and email formatting.
- Copied from legacy templates. Older website templates and email templates sometimes included VML for rounded corners or background images in Internet Explorer.
- Generated by outdated WYSIWYG editors that targeted older IE versions.
If you’re working with HTML email templates, note that some email clients (notably older Outlook desktop versions) still use the Word rendering engine and may rely on VML for certain effects like background images. In that context, you may intentionally keep VML in your email source — but be aware that the markup will not pass HTML5 validation. For web pages, there is no reason to retain these attributes.
In XML and XHTML, the xmlns attribute is used to declare namespaces that allow elements and attributes from different vocabularies to coexist without naming conflicts. The xmlns:o prefix specifically declares the Microsoft Office namespace (urn:schemas-microsoft-com:office:office), which enables Office-specific elements and attributes like <o:p> (Office paragraph markers) within the document.
HTML5, however, is not an XML language. The HTML5 specification only permits the xmlns attribute on the <html> element (and only with the value http://www.w3.org/1999/xhtml), along with xmlns:xlink on SVG elements. Custom namespace prefixes like xmlns:o, xmlns:v (VML), and xmlns:w (Word) are not recognized as valid attributes on any HTML5 element and will trigger validation errors.
Why This Happens
This issue most commonly occurs when:
- Content is pasted from Microsoft Word into a WYSIWYG editor or CMS. Word generates its own flavor of HTML that includes Office namespace declarations and proprietary elements.
- HTML files are exported from Office applications like Word, Excel, or Outlook. These exports produce markup heavily reliant on Office-specific namespaces.
- Email templates are built using tools that generate Office-compatible HTML, carrying namespace baggage into standard web pages.
Why It Matters
- Standards compliance: HTML5 does not support arbitrary XML namespace declarations, so these attributes make your document invalid.
- Bloated markup: Office-generated HTML often includes not just the namespace declarations but also large amounts of Office-specific elements, conditional comments, and inline styles that increase page size significantly.
- Maintenance difficulty: Office-flavored HTML is harder to read, edit, and maintain compared to clean, standards-compliant markup.
- Potential rendering issues: While browsers generally ignore unrecognized attributes, the accompanying Office-specific elements (like <o:p>) can occasionally cause unexpected spacing or layout behavior.
How to Fix It
- Remove all xmlns:o attributes from your HTML elements.
- Remove any Office-specific elements such as <o:p>, <o:SmartTagType>, or similar tags that rely on the Office namespace.
- Check for other Office namespaces like xmlns:v, xmlns:w, xmlns:m, and xmlns:st1 — these should also be removed.
- Clean pasted content before inserting it into your HTML. Many text editors and CMS platforms offer a “Paste as plain text” option that strips out Office formatting.
- If you use a CMS or rich text editor, look for a “Clean up Word HTML” or similar feature to automatically strip Office artifacts.
Examples
❌ Invalid: Office namespace declarations on elements
<!DOCTYPE html>
<html lang="en">
<head>
<title>Company Report</title>
</head>
<body xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:w="urn:schemas-microsoft-com:office:word">
<h1>Quarterly Report</h1>
<p class="MsoNormal">Revenue increased by 15% this quarter.<o:p></o:p></p>
<p class="MsoNormal">Expenses remained stable.<o:p></o:p></p>
</body>
</html>
This markup contains three Office namespace declarations on the <body> element and uses <o:p> elements inside paragraphs — all of which are invalid in HTML5.
✅ Valid: Clean HTML without Office namespaces
<!DOCTYPE html>
<html lang="en">
<head>
<title>Company Report</title>
</head>
<body>
<h1>Quarterly Report</h1>
<p>Revenue increased by 15% this quarter.</p>
<p>Expenses remained stable.</p>
</body>
</html>
❌ Invalid: Namespace on a div element
<div xmlns:o="urn:schemas-microsoft-com:office:office">
<p class="MsoNormal">Meeting notes from Tuesday.<o:p></o:p></p>
</div>
✅ Valid: Clean version
<div>
<p>Meeting notes from Tuesday.</p>
</div>
Notice that in the corrected examples, the class="MsoNormal" attribute was also removed. While MsoNormal is technically a valid class name that won’t cause a validation error, it’s an Office-generated class with no purpose unless you have corresponding CSS rules — removing it keeps your markup clean.
If you genuinely need XML namespace support for document processing, use a proper XHTML document served with the application/xhtml+xml content type, or handle the XML processing separately from your web-facing HTML.
In older XHTML documents, XML namespaces were declared using the xmlns attribute with a prefix, such as xmlns:og="http://ogp.me/ns#". While this syntax was valid in XHTML, HTML5 does not support custom XML namespace declarations on the <html> element. The W3C validator will flag any xmlns:* prefixed attribute (like xmlns:og, xmlns:fb, etc.) as invalid because HTML5 has a strictly defined set of allowed attributes on the <html> element.
The Open Graph Protocol, originally developed by Facebook, allows web pages to become rich objects in a social graph. Major platforms like Facebook, Twitter, LinkedIn, and others rely on Open Graph <meta> tags to generate link previews. The official Open Graph Protocol specification recommends using the prefix attribute on the <html> element instead of the xmlns:og namespace declaration.
The prefix attribute is part of the RDFa specification and is recognized by HTML5 as a valid way to declare vocabulary prefixes. By switching to prefix="og: https://ogp.me/ns#", you maintain full Open Graph functionality while keeping your HTML valid.
How to fix it
- Locate the <html> tag in your document.
- Remove any xmlns:og (or similar xmlns:*) attributes.
- Add or update the prefix attribute with the appropriate namespace declaration.
If you use multiple prefixes (e.g., Open Graph and Facebook-specific tags), you can combine them in a single prefix attribute, separated by a space.
Examples
❌ Invalid: Using xmlns:og (XHTML-style namespace)
<!DOCTYPE html>
<html lang="en" xmlns:og="http://ogp.me/ns#">
<head>
<title>My Page</title>
<meta property="og:title" content="My Page Title" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://example.com/" />
<meta property="og:image" content="https://example.com/image.jpg" />
</head>
<body>
<p>Page content here.</p>
</body>
</html>
This triggers the validator error: Attribute “xmlns:og” not allowed here.
✅ Valid: Using the prefix attribute
<!DOCTYPE html>
<html lang="en" prefix="og: https://ogp.me/ns#">
<head>
<title>My Page</title>
<meta property="og:title" content="My Page Title" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://example.com/" />
<meta property="og:image" content="https://example.com/image.jpg" />
</head>
<body>
<p>Page content here.</p>
</body>
</html>
✅ Valid: Multiple prefixes (Open Graph and Facebook)
If you also use Facebook-specific meta tags (like fb:app_id), declare both prefixes:
<!DOCTYPE html>
<html lang="en" prefix="og: https://ogp.me/ns# fb: https://ogp.me/ns/fb#">
<head>
<title>The Rock (1996)</title>
<meta property="fb:app_id" content="123456789" />
<meta property="og:title" content="The Rock" />
<meta property="og:type" content="video.movie" />
<meta property="og:url" content="https://www.imdb.com/title/tt0117500/" />
<meta property="og:image" content="https://ia.media-imdb.com/images/rock.jpg" />
</head>
<body>
<p>Page content here.</p>
</body>
</html>
❌ Invalid: Multiple xmlns declarations
This older pattern with multiple namespace declarations is equally invalid in HTML5:
<html xmlns:og="http://ogp.me/ns#" xmlns:fb="http://ogp.me/ns/fb#">
Replace it with the single prefix attribute as shown in the examples above.
The <a> element is an interactive content element — it’s already focusable and keyboard-navigable by default. When you place an element with a tabindex attribute inside a link, you create a nested focus target. This means that keyboard users and screen readers encounter two (or more) focusable items where only one is expected. The browser may not handle this consistently, and the user experience becomes unpredictable: should pressing Enter activate the link, or the inner focusable element?
The HTML specification defines that certain interactive elements must not be nested within other interactive elements. An <a> element’s content model explicitly forbids interactive content as descendants. Adding tabindex to any element makes it interactive (focusable), which violates this rule.
This matters for several reasons:
- Accessibility: Screen readers may announce nested focusable elements in confusing ways, or skip them entirely. Users relying on keyboard navigation may get trapped or confused by unexpected tab stops inside a link.
- Standards compliance: The W3C validator flags this as an error because it violates the HTML content model for anchor elements.
- Browser inconsistency: Different browsers may handle nested focusable elements differently, leading to unpredictable behavior across platforms.
To fix this issue, you have a few options:
- Remove the tabindex attribute from the descendant element if it doesn’t need to be independently focusable.
- Restructure your markup so the focusable element is a sibling of the <a> element rather than a descendant.
- Rethink the design — if you need multiple interactive targets in the same area, consider using separate elements styled to appear as a single unit.
Examples
❌ Invalid: Element with tabindex inside an <a> element
<a href="/products">
<div tabindex="0">
<h2>Our Products</h2>
<p>Browse our full catalog</p>
</div>
</a>
The <div> has tabindex="0", making it focusable inside an already-focusable <a> element. This creates conflicting focus targets.
✅ Fixed: Remove tabindex from the descendant
<a href="/products">
<div>
<h2>Our Products</h2>
<p>Browse our full catalog</p>
</div>
</a>
Since the <a> element is already focusable and clickable, the inner <div> doesn’t need its own tabindex. Removing it resolves the conflict.
❌ Invalid: span with tabindex inside a link
<a href="/profile">
<span tabindex="-1">User Name</span>
</a>
Even tabindex="-1" (which removes the element from the natural tab order but still makes it programmatically focusable) triggers this validation error when used inside an <a> element.
✅ Fixed: Remove tabindex from the span
<a href="/profile">
<span>User Name</span>
</a>
❌ Invalid: Button-like element nested inside a link
<a href="/dashboard">
<div tabindex="0" role="button">Settings</div>
</a>
✅ Fixed: Separate the interactive elements
<div class="card-actions">
<a href="/dashboard">Dashboard</a>
<button type="button">Settings</button>
</div>
If you truly need two distinct interactive elements, place them as siblings rather than nesting one inside the other. Use CSS to style them as a cohesive visual unit if needed.
The HTML specification defines button as an interactive content element that accepts phrasing content as its children, but explicitly forbids interactive content as descendants. When you add a tabindex attribute to an element, you make it focusable and potentially interactive, which violates this content model restriction.
This rule exists for important reasons. A button element is a single interactive control — when a user presses Tab, the entire button receives focus as one unit. If elements inside the button also have tabindex, screen readers and keyboard users encounter nested focusable items within what should be a single action target. This creates confusing, unpredictable behavior: users may tab into the button’s internals without understanding the context, and assistive technologies may announce the inner elements separately, breaking the expected interaction pattern.
Browsers may also handle nested focusable elements inconsistently. Some may ignore the inner tabindex, while others may allow focus on the nested element but not properly trigger the button’s click handler, leading to broken functionality.
How to fix it
The most straightforward fix is to remove the tabindex attribute from any elements inside the button. If the inner element was given tabindex="0" to make it focusable, it doesn’t need it — the button itself is already focusable. If it was given tabindex="-1" to programmatically manage focus, reconsider whether that focus management is necessary within a button context.
If you genuinely need multiple interactive elements in the same area, restructure your markup so that the interactive elements are siblings rather than nested inside a button.
Examples
❌ Incorrect: span with tabindex inside a button
<button type="button">
<span tabindex="0">Click me</span>
</button>
The span has tabindex="0", making it a focusable descendant of the button. This violates the content model.
✅ Correct: Remove tabindex from the descendant
<button type="button">
<span>Click me</span>
</button>
The span no longer has tabindex, so the button behaves as a single focusable control.
❌ Incorrect: Multiple focusable elements inside a button
<button type="button">
<span tabindex="0" class="icon">★</span>
<span tabindex="-1" class="label">Favorite</span>
</button>
Both inner span elements have tabindex attributes, which is invalid regardless of the tabindex value.
✅ Correct: Style inner elements without making them focusable
<button type="button">
<span class="icon">★</span>
<span class="label">Favorite</span>
</button>
✅ Correct: Restructure if separate interactions are needed
If you need an icon and a separate action side by side, use sibling elements instead of nesting:
<span class="icon" aria-hidden="true">★</span>
<button type="button">Favorite</button>
This keeps the button as a clean, single interactive element while placing the decorative icon outside of it.
The <main> element represents the dominant, unique content of a document — the primary content that is directly related to or expands upon the central topic of the page. Having more than one visible <main> element creates ambiguity: browsers, screen readers, and other assistive technologies use <main> to identify the primary content area, and multiple visible instances make it unclear which content block is truly the main one.
This is particularly important for accessibility. Screen reader users often rely on landmark navigation to jump directly to the main content of a page. When multiple visible <main> elements exist, this shortcut becomes unreliable or confusing, as the user has no way to know which <main> holds the content they’re looking for.
There are legitimate scenarios where multiple <main> elements make sense — for example, in single-page applications (SPAs) where different views are swapped in and out dynamically using JavaScript. The HTML specification accommodates this by allowing multiple <main> elements as long as only one is visible at a time. The others must be hidden using the hidden attribute.
How to fix it
- If you only need one main content area, remove the extra <main> elements and keep just one.
- If you need multiple views (e.g., for tabbed content or SPA-style navigation), add the hidden attribute to all <main> elements except the one currently active. Use JavaScript to toggle visibility by adding or removing the hidden attribute as needed.
Examples
❌ Invalid: Two visible <main> elements
<header>
<h1>My Website</h1>
</header>
<main>
<h2>Welcome</h2>
<p>This is the home page content.</p>
</main>
<main>
<h2>About</h2>
<p>This is the about page content.</p>
</main>
Both <main> elements are visible, which violates the specification and confuses assistive technologies.
✅ Fixed: Single <main> element
If you don’t need multiple views, simply use one <main>:
<header>
<h1>My Website</h1>
</header>
<main>
<h2>Welcome</h2>
<p>This is the home page content.</p>
</main>
✅ Fixed: Multiple <main> elements with only one visible
If you need multiple views for JavaScript-driven navigation, hide all but the active one using the hidden attribute:
<header>
<nav>
<button onclick="switchView('home')">Home</button>
<button onclick="switchView('about')">About</button>
</nav>
</header>
<main id="home">
<h2>Welcome</h2>
<p>This is the home page content.</p>
</main>
<main id="about" hidden>
<h2>About</h2>
<p>This is the about page content.</p>
</main>
In this pattern, JavaScript would toggle the hidden attribute when the user navigates between views, ensuring only one <main> is ever visible at a time.
❌ Invalid: Using CSS instead of hidden
Note that hiding a <main> element with CSS (e.g., display: none or visibility: hidden) does not satisfy the HTML specification. The validator checks for the hidden attribute, not CSS styles:
<!-- This still triggers the validation error -->
<main>
<h2>Welcome</h2>
</main>
<main style="display: none;">
<h2>About</h2>
</main>
Always use the hidden attribute to indicate that a <main> element is not currently relevant to the page.
The WAI-ARIA specification defines strict rules about which elements can be children of interactive roles. An element with role="button" is treated as an interactive control, and the <a> element (when it has an href) is also an interactive control. Nesting one interactive element inside another creates what’s known as an interactive content nesting violation. Screen readers and other assistive technologies cannot reliably determine user intent when they encounter this pattern — should they announce a button, a link, or both? The result is unpredictable behavior that can make your content inaccessible.
This issue commonly appears when developers wrap links inside styled containers that have been given role="button", or when using component libraries that apply button semantics to wrapper elements containing anchor tags.
Beyond accessibility, browsers themselves handle nested interactive elements inconsistently. Some may ignore the outer interactive role, while others may prevent the inner link from functioning correctly. This makes the pattern unreliable even for users who don’t rely on assistive technologies.
How to fix it
There are several approaches depending on your intent:
- If the element should navigate to a URL, use an <a> element and style it to look like a button. Remove the role="button" from the parent or eliminate the parent wrapper entirely.
- If the element should perform an action (not navigation), use a <button> element or an element with role="button", and handle the action with JavaScript. Remove the nested <a> tag.
- If you need both a button container and a link, flatten the structure so they are siblings rather than nested.
When using role="button" on a non-button element, remember that you must also handle keyboard interaction (Enter and Space key presses) and include tabindex="0" to make it focusable.
Examples
❌ Incorrect: link nested inside a button role
<div role="button">
<a href="/dashboard">Go to Dashboard</a>
</div>
This triggers the validation error because the <a> is a descendant of an element with role="button".
✅ Fix 1: Use a styled link instead
If the intent is navigation, remove the button role and let the <a> element do the job. Style it to look like a button with CSS.
<a href="/dashboard" class="btn">Go to Dashboard</a>
This is the simplest and most semantically correct fix when the purpose is to navigate the user to another page.
✅ Fix 2: Use a real button for actions
If the intent is to trigger an action (not navigate), replace the entire structure with a <button> element.
<button type="button" class="btn" onclick="navigateToDashboard()">
Go to Dashboard
</button>
✅ Fix 3: Use a div with role="button" without nested interactive elements
If you need a custom button using role="button", make sure it contains no interactive descendants.
<div role="button" tabindex="0">
Go to Dashboard
</div>
When using this approach, you must also add keyboard event handlers for Enter and Space to match native button behavior.
❌ Incorrect: link inside a button element
The same principle applies to native <button> elements, not just elements with role="button":
<button>
<a href="/settings">Settings</a>
</button>
✅ Fixed: choose one interactive element
<a href="/settings" class="btn">Settings</a>
❌ Incorrect: deeply nested link
The error applies to any level of nesting, not just direct children:
<div role="button">
<span class="icon-wrapper">
<a href="/help">Help</a>
</span>
</div>
✅ Fixed: flatten the structure
<a href="/help" class="btn">
<span class="icon-wrapper">Help</span>
</a>
As a rule of thumb, every interactive element in your page should have a single, clear role. If something looks like a button but navigates to a URL, make it an <a> styled as a button. If it performs an in-page action, make it a <button>. Keeping these roles distinct ensures your HTML is valid, accessible, and behaves consistently across browsers and assistive technologies.
The role="button" attribute tells assistive technologies like screen readers that an element behaves as a button — a widget used to perform actions such as submitting a form, opening a dialog, or triggering a command. When a <button> element appears inside an element with role="button", the result is a nested interactive control. The HTML specification explicitly forbids this because interactive content must not be nested within other interactive content.
This nesting causes real problems. Screen readers may announce the outer element as a button but fail to recognize or reach the inner <button>. Keyboard users may not be able to focus on or activate the inner control. Different browsers handle the situation inconsistently — some may ignore one of the controls entirely, others may fire events on the wrong element. The end result is an interface that is broken for many users.
This issue commonly arises in a few scenarios:
- A <div> or <span> is given role="button" and then a <button> is placed inside it for styling or click-handling purposes.
- A component library wraps content in a role="button" container, and a developer adds a <button> inside without realizing the conflict.
- A custom card or list item is made clickable with role="button", but also contains action buttons within it.
The fix depends on your intent. If the outer element is the intended interactive control, remove the inner <button> and handle interactions on the outer element. If the inner <button> is the intended control, remove role="button" from the ancestor. If both need to be independently clickable, restructure the markup so neither is a descendant of the other.
Examples
❌ Incorrect: <button> inside an element with role="button"
<div role="button" tabindex="0" onclick="handleClick()">
<button type="button">Click me</button>
</div>
This is invalid because the <button> is a descendant of the <div> that has role="button".
✅ Fix option 1: Use only the <button> element
If the inner <button> is the actual control, remove role="button" from the wrapper:
<div>
<button type="button" onclick="handleClick()">Click me</button>
</div>
✅ Fix option 2: Use only the outer role="button" element
If the outer element is the intended interactive control, remove the inner <button>:
<div role="button" tabindex="0" onclick="handleClick()">
Click me
</div>
Note that when using role="button" on a non-<button> element, you must also handle keyboard events (Enter and Space) manually. A native <button> provides this for free, so prefer option 1 when possible.
❌ Incorrect: Clickable card containing action buttons
<div role="button" tabindex="0" class="card">
<h3>Item title</h3>
<p>Description text</p>
<button type="button">Delete</button>
</div>
✅ Fix: Separate the card link from the action buttons
<div class="card">
<h3><button type="button" class="card-link">Item title</button></h3>
<p>Description text</p>
<button type="button">Delete</button>
</div>
In this approach, the card’s main action is handled by a <button> on the title, while the “Delete” button remains an independent control. Neither is nested inside the other, and both are accessible to keyboard and screen reader users.
The HTML specification defines strict rules about which elements can be nested inside others. The <a> element is classified as interactive content, and its content model explicitly forbids other interactive content as descendants. Elements with role="button" — whether a native <button> or any element with the ARIA role — are also interactive. Nesting one inside the other creates an ambiguous situation: should a click activate the link or the button?
This matters for several important reasons:
- Accessibility: Screen readers and assistive technologies rely on a clear, unambiguous element hierarchy. When a button is inside a link, the announced role becomes confusing — users may hear both a link and a button, or the assistive technology may only expose one of them, hiding the other’s functionality.
- Unpredictable behavior: Browsers handle nested interactive elements inconsistently. Some may split the elements apart in the DOM, while others may swallow click events. This leads to broken or unreliable behavior across different browsers.
- Standards compliance: The WHATWG HTML Living Standard and W3C HTML specification both explicitly prohibit this nesting pattern.
To fix this issue, decide what the element should actually do. If it navigates to a URL, use an <a> element. If it performs an action (like submitting a form or triggering JavaScript), use a <button>. If you need both visual styles, use CSS to style one element to look like the other.
Examples
❌ Incorrect: Element with role="button" inside an <a>
<a href="/dashboard">
<span role="button">Go to Dashboard</span>
</a>
The <span> with role="button" is interactive content nested inside the interactive <a> element.
❌ Incorrect: <button> inside an <a>
<a href="/settings">
<button>Open Settings</button>
</a>
A <button> element is inherently interactive and must not appear inside an <a>.
✅ Correct: Use a link styled as a button
If the purpose is navigation, use the <a> element and style it with CSS:
<a href="/dashboard" class="btn">Go to Dashboard</a>
✅ Correct: Use a button that navigates via JavaScript
If you need a button that also navigates, handle navigation in JavaScript:
<button type="button" onclick="location.href='/dashboard'">Go to Dashboard</button>
✅ Correct: Place elements side by side
If you truly need both a link and a button, keep them as siblings rather than nesting one inside the other:
<div class="actions">
<a href="/dashboard">Go to Dashboard</a>
<button type="button">Save Preference</button>
</div>
✅ Correct: Link styled as a button using ARIA (when appropriate)
If the element navigates but you want assistive technologies to announce it as a button, you can apply role="button" directly to the element — just don’t nest it inside an <a>:
<span role="button" tabindex="0" onclick="location.href='/dashboard'">
Go to Dashboard
</span>
Note that using role="button" on a non-interactive element like <span> requires you to also add tabindex="0" for keyboard focusability and handle keydown events for the Enter and Space keys. In most cases, a native <a> or <button> element is the better choice.
The ARIA button role tells browsers and assistive technologies that an element behaves like a button. According to the WAI-ARIA specification, elements with role="button" follow the same content restrictions as native <button> elements. Specifically, they must not contain interactive content as descendants. The <input> element is considered interactive content, so nesting it inside any element with role="button" is invalid.
This restriction exists for important accessibility and usability reasons. When a screen reader encounters an element with role="button", it announces it as a single actionable control. If that button contains another interactive element like an <input>, the user faces conflicting interactions — should activating the element trigger the button action or interact with the input? This ambiguity confuses both assistive technologies and users. Browsers may also handle focus and click events unpredictably when interactive elements are nested this way.
The same rule applies to native <button> elements, <a> elements with an href, and any other element that is already interactive. Adding role="button" to a <div> or <span> elevates it to the same status, so the same nesting restrictions apply.
To fix this issue, consider these approaches:
- Move the <input> outside the button-role element and position them as siblings.
- Replace the <input> with non-interactive content such as a <span> styled to look like the desired control, with appropriate ARIA attributes to convey state.
- Rethink the component structure — if you need both a button action and an input, they should be separate controls that are visually grouped but not nested.
Examples
❌ Invalid: <input> nested inside an element with role="button"
<div role="button" tabindex="0">
<input type="checkbox" />
Accept terms
</div>
❌ Invalid: <input> nested inside a native <button>
<button>
<input type="text" />
Search
</button>
✅ Valid: Separate the <input> and button into sibling elements
<label>
<input type="checkbox" />
Accept terms
</label>
<button>Submit</button>
✅ Valid: Use non-interactive content inside the button-role element
If you want a toggle-style button that conveys checked/unchecked state, use ARIA attributes on the button itself instead of embedding an <input>:
<div role="button" tabindex="0" aria-pressed="false">
<span aria-hidden="true">☐</span>
Accept terms
</div>
✅ Valid: Use a <label> and <input> alongside a button
<div>
<label>
<input type="checkbox" />
Accept terms
</label>
<div role="button" tabindex="0">Continue</div>
</div>
✅ Valid: Button with only non-interactive phrasing content
<div role="button" tabindex="0">
<span>Click me</span>
</div>
When in doubt, keep interactive elements as separate, distinct controls rather than nesting them. This ensures clear semantics, predictable behavior across browsers, and a good experience for users of assistive technologies.
The itemtype and itemscope attributes are part of the HTML Microdata specification, which allows you to embed structured, machine-readable data into your HTML. The itemscope attribute creates a new item — it defines a scope within which properties (via itemprop) are associated. The itemtype attribute then specifies a vocabulary URL (typically from Schema.org) that describes what kind of item it is.
According to the WHATWG HTML Living Standard, itemtype has no meaning without itemscope. The itemscope attribute is what establishes the element as a microdata item container. Without it, itemtype has nothing to qualify — there is no item to assign a type to. This is why the spec requires itemscope to be present whenever itemtype is used.
Getting this wrong matters for several reasons:
- Structured data won’t work. Search engines like Google rely on valid microdata to generate rich results (e.g., star ratings, event details, product prices). Invalid markup means your structured data will be silently ignored.
- Standards compliance. Using itemtype without itemscope violates the HTML specification, and validators will flag it as an error.
- Maintainability. Other developers (or your future self) may assume the microdata is functioning correctly when it isn’t.
To fix this issue, you have two options:
- Add itemscope to the element — this is the correct fix if you intend to use microdata.
- Remove itemtype — this is appropriate if you don’t actually need structured data on that element.
Examples
Incorrect — itemtype without itemscope
This triggers the validation error because itemscope is missing:
<div itemtype="https://schema.org/Person">
<p><span itemprop="name">Liza Jane</span></p>
<p><span itemprop="email">liza.jane@example.com</span></p>
</div>
Correct — adding itemscope alongside itemtype
Adding the itemscope attribute establishes the element as a microdata item, making itemtype valid:
<div itemscope itemtype="https://schema.org/Person">
<p><span itemprop="name">Liza Jane</span></p>
<p><span itemprop="email">liza.jane@example.com</span></p>
</div>
Here, itemscope tells parsers that this div contains a microdata item, and itemtype="https://schema.org/Person" specifies that the item is a Person with properties like name and email.
Correct — removing itemtype when structured data isn’t needed
If you don’t need typed structured data, simply remove the itemtype attribute. You can still use itemscope on its own to create an untyped item, or remove both attributes entirely:
<div>
<p><span>Liza Jane</span></p>
<p><span>liza.jane@example.com</span></p>
</div>
Correct — nested items with itemscope and itemtype
When nesting microdata items, each level that uses itemtype must also have itemscope:
<div itemscope itemtype="https://schema.org/Organization">
<span itemprop="name">Acme Corp</span>
<div itemprop="founder" itemscope itemtype="https://schema.org/Person">
<span itemprop="name">Liza Jane</span>
</div>
</div>
Notice that both the outer div (the Organization) and the inner div (the Person) have itemscope paired with their respective itemtype values. Omitting itemscope from either element would trigger the validation error.
The name attribute on an <input> element identifies the form control’s data when the form is submitted. It acts as the key in the key-value pair sent to the server (e.g., email=user@example.com). When you set name="", the attribute is present but contains an empty string, which the HTML specification considers an invalid value. An empty name means the input’s data will be excluded from the form’s submission payload in most browsers, making it functionally useless for form processing.
This issue matters for several reasons:
- Form functionality: Inputs with empty names are typically omitted from form data, so the server never receives the user’s input.
- Standards compliance: The HTML specification requires that if the name attribute is present, its value must not be empty.
- JavaScript references: An empty name makes it difficult to reference the element using methods like document.getElementsByName() or FormData.
- Accessibility: Screen readers and assistive technologies may use the name attribute to help identify form controls, and an empty value provides no useful information.
Note that the name attribute is not technically required on every <input> element — it’s perfectly valid to omit it entirely. For example, inputs used purely for client-side JavaScript interactions without form submission don’t need a name. The error specifically arises when the attribute is present but set to an empty string.
To fix the issue, either assign a meaningful name that describes the data the input collects, or remove the name attribute altogether if the input isn’t part of a form submission.
Examples
❌ Empty name attribute triggers the error
<form action="/submit" method="post">
<label for="email">Email:</label>
<input type="email" id="email" name="">
<button type="submit">Submit</button>
</form>
✅ Providing a meaningful name value
<form action="/submit" method="post">
<label for="email">Email:</label>
<input type="email" id="email" name="email">
<button type="submit">Submit</button>
</form>
✅ Removing name when the input isn’t submitted
If the input is only used for client-side interaction and doesn’t need to be part of form data, simply omit the attribute:
<label for="search">Filter results:</label>
<input type="text" id="search">
❌ Multiple inputs with empty names
This pattern sometimes appears when inputs are generated dynamically with placeholder attributes:
<form action="/register" method="post">
<input type="text" name="">
<input type="password" name="">
<button type="submit">Register</button>
</form>
✅ Each input gets a descriptive name
<form action="/register" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="username">
<label for="password">Password:</label>
<input type="password" id="password" name="password">
<button type="submit">Register</button>
</form>
The <link> element is used to define relationships between the current document and external resources—most commonly stylesheets, favicons, and preloaded assets. It is a void element (it has no closing tag) and it produces no rendered content on the page. Because <link> elements are inherently non-visible and already absent from the accessibility tree, the aria-hidden attribute serves no purpose on them.
The aria-hidden attribute is designed to control the visibility of rendered content for assistive technologies like screen readers. When set to "true" on a visible element, it tells assistive technologies to skip that element and its descendants. Applying it to a <link> element is contradictory—you’re trying to hide something from assistive technologies that was never exposed to them in the first place. The HTML specification explicitly disallows this combination, and the W3C validator will flag it as an error.
This issue sometimes arises when developers apply aria-hidden broadly through templating systems, JavaScript frameworks, or build tools that inject attributes across multiple element types without distinguishing between visible content elements and metadata elements. It can also happen when copying attribute patterns from <a> elements (which share the word “link” conceptually but are entirely different elements) onto <link> elements.
How to fix it
The fix is straightforward: remove the aria-hidden attribute from the <link> element. No replacement or alternative attribute is needed because <link> elements are already invisible to assistive technologies.
If your original intent was to hide a visible element from screen readers, make sure you’re applying aria-hidden to the correct element—a rendered content element such as <div>, <span>, <img>, or <a>, not a metadata element like <link>.
Examples
Incorrect: aria-hidden on a <link> element
<link rel="stylesheet" href="styles.css" aria-hidden="true">
<link rel="icon" href="favicon.ico" aria-hidden="true">
<link rel="preload" href="font.woff2" as="font" type="font/woff2" aria-hidden="true">
Correct: <link> without aria-hidden
<link rel="stylesheet" href="styles.css">
<link rel="icon" href="favicon.ico">
<link rel="preload" href="font.woff2" as="font" type="font/woff2">
Correct: aria-hidden used on a visible element instead
If you need to hide a decorative element from screen readers, apply aria-hidden to the rendered element itself:
<link rel="stylesheet" href="styles.css">
<div aria-hidden="true">
<img src="decorative-swirl.png" alt="">
</div>
Incorrect vs. correct in a full document
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
<!-- Incorrect: aria-hidden on link -->
<link rel="stylesheet" href="styles.css" aria-hidden="true">
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Page</title>
<!-- Correct: no aria-hidden on link -->
<link rel="stylesheet" href="styles.css">
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
The sizes attribute works together with the srcset attribute to help the browser choose the most appropriate image source before the page layout is calculated. It describes how wide the image will be displayed under various viewport conditions, allowing the browser to pick an optimally sized image from the candidates listed in srcset. According to the HTML specification, the value of sizes must be a valid source size list — a comma-separated set of media conditions paired with lengths, with a default length at the end. An empty string ("") does not satisfy this requirement and is therefore invalid.
When the browser encounters an empty sizes attribute, it cannot determine the intended display width of the image. This defeats the purpose of responsive images and may cause the browser to fall back to a default behavior (typically assuming 100vw), which could result in downloading an unnecessarily large image. Beyond the functional issue, an empty attribute signals a code quality problem — it often means a template is outputting the attribute even when no value has been configured.
Why this matters
- Standards compliance: The HTML specification explicitly requires sizes to be a non-empty, valid source size list when present. An empty value is a parse error.
- Performance: Without a proper sizes value, the browser cannot make an informed decision about which srcset candidate to download. This can lead to wasted bandwidth and slower page loads, especially on mobile devices.
- Code quality: Empty attributes clutter your markup and suggest missing configuration, making the code harder to maintain.
How to fix it
- Provide a valid sizes value that describes how wide the image will actually render at different viewport widths.
- Remove sizes entirely if you are not using width descriptors (w) in srcset. When srcset uses pixel density descriptors (1x, 2x), the sizes attribute is not needed.
- Remove both sizes and srcset if you don’t need responsive image selection at all.
Examples
❌ Empty sizes attribute
<img
src="photo.jpg"
srcset="photo-small.jpg 480w, photo-large.jpg 1200w"
sizes=""
alt="A mountain landscape">
The empty sizes="" triggers the validation error.
✅ Valid sizes with a single default value
If the image always takes up the full viewport width:
<img
src="photo.jpg"
srcset="photo-small.jpg 480w, photo-large.jpg 1200w"
sizes="100vw"
alt="A mountain landscape">
✅ Valid sizes with media conditions
If the image displays at different widths depending on the viewport:
<img
src="photo.jpg"
srcset="photo-small.jpg 480w, photo-medium.jpg 800w, photo-large.jpg 1200w"
sizes="(max-width: 600px) 100vw, (max-width: 1024px) 50vw, 33vw"
alt="A mountain landscape">
This tells the browser: use 100vw on viewports up to 600px, 50vw up to 1024px, and 33vw otherwise.
✅ Removing sizes when using density descriptors
When srcset uses density descriptors instead of width descriptors, sizes is not applicable and should be omitted:
<img
src="photo.jpg"
srcset="photo.jpg 1x, photo-2x.jpg 2x"
alt="A mountain landscape">
✅ Removing both attributes when not needed
If responsive image selection isn’t required, simply use a standard <img>:
<img src="photo.jpg" alt="A mountain landscape">
Common template fix
If your CMS or templating system conditionally outputs these attributes, ensure the sizes attribute is only rendered when it has a value. For example, in a template:
<!-- Before (always outputs sizes, even when empty) -->
<img src="photo.jpg" srcset="{{srcset}}" sizes="{{sizes}}" alt="{{alt}}">
<!-- After (only outputs sizes when it has a value) -->
<img src="photo.jpg" srcset="{{srcset}}" {{#if sizes}}sizes="{{sizes}}"{{/if}} alt="{{alt}}">
The HTML specification requires that if the name attribute is used on a <form> element, its value must be a non-empty string. An empty name="" attribute serves no practical purpose — it doesn’t register the form in the document.forms named collection, and it can’t be used as a valid reference in scripts. The W3C validator flags this as an error because it violates the content model defined in the WHATWG HTML Living Standard.
The name attribute on a form is primarily used to access the form programmatically through document.forms["formName"]. When the value is empty, this lookup mechanism doesn’t work, so the attribute becomes meaningless. This is different from the id attribute, which also identifies elements but participates in fragment navigation and CSS targeting. The name attribute on <form> is specifically for the legacy document.forms named getter interface.
It’s worth noting that the name attribute value on a form must not equal an empty string, and it should be unique among the form elements in the forms collection. While duplicate names won’t cause a validation error, they can lead to unexpected behavior when accessing forms by name in JavaScript.
How to fix
You have two options:
- Remove the attribute entirely. If you’re not referencing the form by name in JavaScript, the name attribute is unnecessary. You can use id instead for CSS or JavaScript targeting.
- Provide a meaningful, non-empty value. If you need to reference the form through document.forms, give it a descriptive name that reflects its purpose.
Examples
❌ Invalid: empty name attribute
<form name="">
<label for="email">Email</label>
<input type="email" id="email" name="email">
<button type="submit">Subscribe</button>
</form>
This triggers the validator error because the name attribute is present but has an empty string value.
✅ Fixed: attribute removed
<form>
<label for="email">Email</label>
<input type="email" id="email" name="email">
<button type="submit">Subscribe</button>
</form>
If you don’t need to reference the form by name, simply remove the attribute.
✅ Fixed: meaningful name provided
<form name="subscriptionForm">
<label for="email">Email</label>
<input type="email" id="email" name="email">
<button type="submit">Subscribe</button>
</form>
With a non-empty name, you can now access the form in JavaScript using document.forms["subscriptionForm"] or document.forms.subscriptionForm.
✅ Alternative: using id instead
<form id="subscription-form">
<label for="email">Email</label>
<input type="email" id="email" name="email">
<button type="submit">Subscribe</button>
</form>
In modern development, using id with document.getElementById() or document.querySelector() is often preferred over the name attribute for form identification. The name attribute on <form> is a legacy feature that remains valid but isn’t required for most use cases.
The imagesizes attribute is used exclusively on <link> elements that have rel="preload" and as="image". It works in tandem with the imagesrcset attribute to allow the browser to preload the most appropriate image from a set of candidates — mirroring how sizes and srcset work on an <img> element. When the browser encounters these attributes on a <link>, it can begin fetching the right image resource early, before it even parses the <img> tag in the document body.
When imagesizes is set to an empty string (""), the browser has no information about the intended display size of the image, which defeats the purpose of responsive image preloading. The browser cannot select the best candidate from imagesrcset without knowing how large the image will be rendered. An empty value is invalid per the HTML specification, which requires the attribute to contain a valid source size list (the same syntax used by the sizes attribute on <img>).
This matters for both performance and standards compliance. Responsive preloading is a performance optimization — an empty imagesizes undermines that by leaving the browser unable to make an informed choice. From a standards perspective, the validator correctly rejects the empty value because the attribute’s defined value space does not include the empty string.
How to fix it
- Provide a valid sizes value that matches the sizes attribute on the corresponding <img> element in your page. This tells the browser how wide the image will be at various viewport widths.
- Remove imagesizes entirely if you don’t need responsive preloading. If you’re preloading a single image (using href instead of imagesrcset), you don’t need imagesizes at all.
Examples
❌ Bad: empty imagesizes attribute
<link
rel="preload"
as="image"
imagesrcset="hero-480.jpg 480w, hero-800.jpg 800w, hero-1200.jpg 1200w"
imagesizes="">
The empty imagesizes="" triggers the validation error and prevents the browser from selecting the correct image candidate.
✅ Fixed: providing a valid sizes value
<link
rel="preload"
as="image"
imagesrcset="hero-480.jpg 480w, hero-800.jpg 800w, hero-1200.jpg 1200w"
imagesizes="(max-width: 600px) 480px, (max-width: 1000px) 800px, 1200px">
The imagesizes value uses the same syntax as the sizes attribute on <img>. It provides media conditions paired with lengths, with a fallback length at the end. This value should match the sizes attribute on the corresponding <img> element in your markup.
✅ Fixed: simple full-width image
<link
rel="preload"
as="image"
imagesrcset="banner-640.jpg 640w, banner-1280.jpg 1280w"
imagesizes="100vw">
If the image spans the full viewport width, 100vw is a straightforward and valid value.
✅ Fixed: removing the attribute when not needed
<link rel="preload" as="image" href="logo.png">
If you’re preloading a single, non-responsive image, omit both imagesrcset and imagesizes and use the href attribute instead. The imagesizes attribute is only meaningful when paired with imagesrcset.
Ready to validate your sites?
Start your free trial today.