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 rel attribute defines the relationship between the current document and a linked resource. The HTML specification maintains a set of recognized keyword values for this attribute, and the allowed keywords vary depending on which element the attribute appears on. For example, stylesheet is valid on <link> but not on <a>, while nofollow is valid on <a> and <form> but not on <link>.
When the validator encounters a rel value that isn't a recognized keyword, it checks whether the value is a valid absolute URL. This is because the HTML specification allows custom link types to be defined using absolute URLs as identifiers (similar to how XML namespaces work). If the value is neither a recognized keyword nor a valid absolute URL, the validator raises this error.
Common causes of this error include:
- Typos in standard keywords — for example,
rel="styelsheet"orrel="no-follow"instead of the correctrel="stylesheet"orrel="nofollow". - Using non-standard or invented values — such as
rel="custom"orrel="external", which aren't part of the HTML specification's recognized set. - Using relative URLs as custom link types — for example,
rel="my-custom-type"instead of a full URL likerel="https://example.com/my-custom-type".
This matters because browsers and other user agents rely on recognized rel values to determine how to handle linked resources. An unrecognized value will simply be ignored, which could mean your stylesheet doesn't load, your prefetch hint doesn't work, or search engines don't respect your intended link relationship. Using correct values ensures predictable behavior across all browsers and tools.
Examples
Incorrect: Misspelled keyword
<link rel="styleshet" href="main.css">
The validator reports that styleshet is not a recognized keyword and is not an absolute URL.
Correct: Fixed spelling
<link rel="stylesheet" href="main.css">
Incorrect: Non-standard keyword on an anchor
<a href="https://example.com" rel="external">Visit Example</a>
The value external is not a standard rel keyword in the HTML specification, so the validator flags it.
Correct: Using a recognized keyword
<a href="https://example.com" rel="noopener">Visit Example</a>
Incorrect: Relative URL as a custom link type
<link rel="my-custom-rel" href="data.json">
Correct: Absolute URL as a custom link type
If you genuinely need a custom relationship type, provide a full absolute URL:
<link rel="https://example.com/rels/my-custom-rel" href="data.json">
Correct: Common valid rel values
Here are some frequently used standard rel keywords with their appropriate elements:
<!-- Linking a stylesheet -->
<link rel="stylesheet" href="styles.css">
<!-- Linking a favicon -->
<link rel="icon" href="favicon.ico">
<!-- Preloading a resource -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
<!-- Telling search engines not to follow a link -->
<a href="https://example.com" rel="nofollow">Sponsored link</a>
<!-- Opening a link safely in a new tab -->
<a href="https://example.com" target="_blank" rel="noopener noreferrer">External site</a>
Multiple rel values
You can specify multiple space-separated rel values. Each one must individually be either a recognized keyword or a valid absolute URL:
<!-- Correct: both values are recognized keywords -->
<a href="https://example.com" target="_blank" rel="noopener noreferrer">External</a>
<!-- Incorrect: "popup" is not a recognized keyword or absolute URL -->
<a href="https://example.com" target="_blank" rel="noopener popup">External</a>
To resolve this error, consult the MDN rel attribute reference for the full list of recognized keywords and which elements support them. If your value isn't on the list, either replace it with the correct standard keyword or use a complete absolute URL to define your custom link type.
The aria-label attribute cannot be used on a custom element like <menu-item> when it has no explicit role attribute, because it defaults to the generic role, which is in the list of roles that prohibit aria-label.
Custom elements without an explicit role are treated as having the generic role (equivalent to a <span> or <div> in terms of semantics). The WAI-ARIA specification prohibits aria-label on several roles, including generic, because naming these elements creates a confusing experience for assistive technology users — a generic container with a label doesn't convey any meaningful purpose.
To fix this, you need to assign a meaningful role to the <menu-item> element that supports accessible naming. Common choices include role="menuitem", role="link", or role="button", depending on what the element actually does. Since this appears to represent a menu item that navigates to a page, role="menuitem" is likely the most appropriate.
HTML Examples
❌ Invalid: aria-label on an element with implicit generic role
<menu-item
submenu-href="/page"
label="some label"
submenu-title="some submenu title"
aria-label="some aria label">
</menu-item>
✅ Valid: adding an explicit role that supports aria-label
<menu-item
role="menuitem"
submenu-href="/page"
label="some label"
submenu-title="some submenu title"
aria-label="some aria label">
</menu-item>
If the aria-label isn't actually needed (for example, if assistive technology already receives the label through other means in your component), another valid fix is to simply remove aria-label entirely.
The WAI-ARIA specification defines a strict ownership model for tab-related roles. An element with role="tab" controls the visibility of an associated role="tabpanel" element, and tabs are expected to be grouped within a tablist. This relationship is how assistive technologies like screen readers understand and communicate the tab interface pattern to users — for example, announcing "tab 2 of 4" when focus moves between tabs.
When a tab is not contained in or owned by a tablist, screen readers cannot determine how many tabs exist in the group, which tab is currently selected, or how to navigate between them. This fundamentally breaks the accessibility of the tab interface, making it confusing or unusable for people who rely on assistive technologies.
There are two ways to establish the required relationship:
- Direct containment: Place the
role="tab"elements as direct children of therole="tablist"element. This is the most common and straightforward approach. - Using
aria-owns: If the DOM structure prevents direct nesting, addaria-ownsto thetablistelement with a space-separated list ofidvalues referencing each tab. This tells assistive technologies that thetablistowns those tabs even though they aren't direct children in the DOM.
Examples
Incorrect: tab outside of a tablist
In this example, the role="tab" buttons are siblings of the tablist rather than children of it, which triggers the validation error.
<div class="tabs">
<div role="tablist" aria-label="Sample Tabs"></div>
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">
First Tab
</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2">
Second Tab
</button>
</div>
Correct: tabs as direct children of tablist
The simplest fix is to place the role="tab" elements directly inside the role="tablist" element.
<div class="tabs">
<div role="tablist" aria-label="Sample Tabs">
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1" tabindex="0">
First Tab
</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2" tabindex="-1">
Second Tab
</button>
</div>
<div id="panel-1" role="tabpanel" tabindex="0" aria-labelledby="tab-1">
<p>Content for the first panel</p>
</div>
<div id="panel-2" role="tabpanel" tabindex="0" aria-labelledby="tab-2" hidden>
<p>Content for the second panel</p>
</div>
</div>
Correct: using aria-owns when DOM nesting isn't possible
If your layout or framework makes it difficult to nest the tabs directly inside the tablist, you can use aria-owns to establish the relationship programmatically.
<div class="tabs">
<div role="tablist" aria-label="Sample Tabs" aria-owns="tab-1 tab-2"></div>
<div class="tab-wrapper">
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1" tabindex="0">
First Tab
</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2" tabindex="-1">
Second Tab
</button>
</div>
<div id="panel-1" role="tabpanel" tabindex="0" aria-labelledby="tab-1">
<p>Content for the first panel</p>
</div>
<div id="panel-2" role="tabpanel" tabindex="0" aria-labelledby="tab-2" hidden>
<p>Content for the second panel</p>
</div>
</div>
Additional notes
- Each
role="tab"element should usearia-selectedto indicate which tab is active ("true") and which are not ("false"). - Use
aria-controlson each tab to reference theidof its associatedtabpanel. - Use
aria-labelledbyon eachtabpanelto point back to its controlling tab. - Set
tabindex="0"on the active tab andtabindex="-1"on inactive tabs to support keyboard navigation with arrow keys within thetablist. - Always include an
aria-labeloraria-labelledbyon thetablistto give it an accessible name.
The <gcse:search> element is a custom tag from Google's Programmable Search Engine widget and is not part of any HTML specification.
Google's Programmable Search Engine (formerly Custom Search Engine) offers two ways to drop a search box onto a page. The older one uses namespaced tags such as <gcse:search>, <gcse:searchbox>, and <gcse:searchresults>, which the loaded cse.js script finds and replaces with the real widget. Browsers do not recognize the gcse: prefix, so they treat the tag as an unknown element, and the W3C validator rejects it because no such element exists in HTML.
Google supports an equivalent form that is valid HTML: a standard <div> carrying a gcse- class, like <div class="gcse-search">. The script reads the class instead of a custom tag name and renders the same search box. Switching to the class form clears the validation error without changing how the widget works.
HTML examples
Invalid: namespaced <gcse:search> element
<script async src="https://cse.google.com/cse.js?cx=YOUR_SEARCH_ENGINE_ID"></script>
<gcse:search></gcse:search>
Valid: div with the gcse-search class
<script async src="https://cse.google.com/cse.js?cx=YOUR_SEARCH_ENGINE_ID"></script>
<div class="gcse-search"></div>
The same applies to the other Programmable Search tags: replace <gcse:searchbox> with <div class="gcse-searchbox"></div> and <gcse:searchresults> with <div class="gcse-searchresults"></div>. Each gcse: element has a matching gcse- class that produces the same result and passes validation.
The preload value of the <link> element's rel attribute lets you declare fetch requests in the HTML <head>, telling the browser to start loading critical resources early in the page lifecycle—before the main rendering machinery kicks in. This can significantly improve performance by ensuring key assets are available sooner and are less likely to block rendering.
However, a preload hint is incomplete without the as attribute. The as attribute tells the browser what kind of resource is being fetched. This matters for several important reasons:
- Request prioritization: Browsers assign different priorities to different resource types. A stylesheet is typically higher priority than an image. Without
as, the browser cannot apply the correct priority, and the preloaded resource may be fetched with a low default priority, undermining the purpose of preloading. - Content Security Policy (CSP): CSP rules are applied based on resource type (e.g.,
script-src,style-src). Without knowing the type, the browser cannot enforce the appropriate policy. - Correct
Acceptheader: Theasvalue determines whichAcceptheader the browser sends with the request. For example, an image request sends a differentAcceptheader than a script request. An incorrect or missing header could lead to unexpected responses from the server. - Cache matching: When the resource is later requested by a
<script>,<link rel="stylesheet">, or other element, the browser needs to match it against the preloaded resource in its cache. Withoutas, the browser may not recognize the preloaded resource and could fetch it again, resulting in a duplicate request—the opposite of what you intended.
The HTML specification explicitly requires the as attribute when rel="preload" is used, making this a conformance error.
Common as Values
The as attribute accepts a specific set of values. Here are the most commonly used ones:
| Value | Resource Type |
|---|---|
script | JavaScript files |
style | CSS stylesheets |
font | Web fonts |
image | Images |
fetch | Resources fetched via fetch() or XMLHttpRequest |
document | HTML documents (for <iframe>) |
audio | Audio files |
video | Video files |
track | WebVTT subtitle tracks |
worker | Web workers or shared workers |
Examples
Incorrect: Missing as attribute
This will trigger the validation error because the browser doesn't know what type of resource is being preloaded:
<link rel="preload" href="/fonts/roboto.woff2">
<link rel="preload" href="/js/app.js">
Correct: as attribute included
Adding the appropriate as value tells the browser exactly what kind of resource to expect:
<link rel="preload" href="/fonts/roboto.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/js/app.js" as="script">
<link rel="preload" href="/css/main.css" as="style">
<link rel="preload" href="/images/hero.webp" as="image">
Note on fonts and crossorigin
When preloading fonts, you must also include the crossorigin attribute, even if the font is hosted on the same origin. This is because font fetches are CORS requests by default. Without crossorigin, the preloaded font won't match the later font request and will be fetched twice:
<!-- Correct: includes both as and crossorigin for fonts -->
<link rel="preload" href="/fonts/roboto.woff2" as="font" type="font/woff2" crossorigin>
Full document example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Preload Example</title>
<link rel="preload" href="/css/main.css" as="style">
<link rel="preload" href="/js/app.js" as="script">
<link rel="preload" href="/fonts/roboto.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="/css/main.css">
</head>
<body>
<h1>Hello, world!</h1>
<script src="/js/app.js"></script>
</body>
</html>
Choosing the correct as value is straightforward—just match it to how the resource will ultimately be used on the page. If you're preloading a stylesheet, use as="style"; if it's a script, use as="script", and so on.
The ARIA specification defines a strict ownership hierarchy for table-related roles. A columnheader represents a header cell for a column, analogous to a <th> element in native HTML. For assistive technologies like screen readers to correctly announce column headers and associate them with the data cells beneath them, the columnheader must exist within a row context. The required structure is:
- An element with
role="table",role="grid", orrole="treegrid"serves as the container. - Inside it, elements with
role="rowgroup"(optional) orrole="row"organize the rows. - Each
role="row"element contains one or more elements withrole="columnheader",role="rowheader", orrole="cell".
When a role="columnheader" element is placed directly inside a role="table" or role="grid" container — or any other element that is not role="row" — the validator raises this error. Without the row wrapper, screen readers cannot navigate the table structure properly. Users who rely on assistive technology may hear disjointed content or miss the column headers entirely, making the data table unusable.
The best practice, whenever feasible, is to use native HTML table elements (<table>, <thead>, <tr>, <th>, <td>). These carry implicit ARIA roles and establish the correct ownership relationships automatically, eliminating this entire category of errors. Only use ARIA table roles when you genuinely cannot use native table markup — for example, when building a custom grid widget with non-table elements for layout reasons.
Examples
Incorrect: columnheader not inside a row
In this example, the columnheader elements are direct children of the table container, with no role="row" wrapper:
<div role="table" aria-label="Employees">
<div role="columnheader">Name</div>
<div role="columnheader">Department</div>
<div role="row">
<div role="cell">Alice</div>
<div role="cell">Engineering</div>
</div>
</div>
Correct: columnheader inside a row
Wrapping the column headers in an element with role="row" fixes the issue:
<div role="table" aria-label="Employees">
<div role="row">
<div role="columnheader">Name</div>
<div role="columnheader">Department</div>
</div>
<div role="row">
<div role="cell">Alice</div>
<div role="cell">Engineering</div>
</div>
</div>
Correct: Using rowgroup for additional structure
You can optionally use role="rowgroup" to separate headers from body rows, similar to <thead> and <tbody>. The columnheader elements must still be inside a row:
<div role="table" aria-label="Employees">
<div role="rowgroup">
<div role="row">
<div role="columnheader">Name</div>
<div role="columnheader">Department</div>
</div>
</div>
<div role="rowgroup">
<div role="row">
<div role="cell">Alice</div>
<div role="cell">Engineering</div>
</div>
</div>
</div>
Best practice: Use native table elements
Native HTML tables have built-in semantics that make ARIA roles unnecessary. A <th> inside a <tr> already behaves as a columnheader inside a row:
<table>
<thead>
<tr>
<th>Name</th>
<th>Department</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alice</td>
<td>Engineering</td>
</tr>
</tbody>
</table>
This approach is simpler, more robust across browsers and assistive technologies, and avoids the risk of ARIA misuse. Reserve ARIA table roles for situations where native table markup is not an option.
In URLs, special characters that aren't part of the standard allowed set must be represented using percent-encoding (also called URL encoding). This works by replacing the character with a % followed by two hexadecimal digits representing its byte value — for example, a space becomes %20, and an ampersand becomes %26. Because % itself serves as the escape character in this scheme, any bare % that isn't followed by two hexadecimal digits creates an ambiguous, invalid URL.
When the W3C validator encounters a src attribute containing a % not followed by two valid hex digits, it cannot determine the intended character and reports the error. This issue typically arises in two scenarios:
- A literal percent sign in the URL — for instance, a query parameter like
?width=48%where the%is meant as an actual percent symbol. The%must be encoded as%25. - Incomplete or corrupted percent-encoding — such as
%2instead of%20, or%GZwhere the characters after%aren't valid hexadecimal digits (only0–9andA–F/a–fare valid).
Why this matters
- Browser inconsistency: While many browsers try to handle malformed URLs gracefully, behavior varies. Some browsers may misinterpret the intended resource path, leading to broken images or unexpected requests.
- Standards compliance: The URL Living Standard and RFC 3986 define strict rules for percent-encoding. Invalid URLs violate these standards.
- Reliability: Proxies, CDNs, and server-side software may reject or misroute requests with malformed URLs, causing images to fail to load in certain environments even if they work in your local browser.
How to fix it
- Find every bare
%in the URL that isn't followed by two hexadecimal digits. - If the
%is meant as a literal percent sign, replace it with%25. - If the
%is part of a broken encoding sequence (e.g.,%2or%GH), correct it to the intended two-digit hex code (e.g.,%20for a space). - Use proper URL encoding functions in your language or framework (e.g.,
encodeURIComponent()in JavaScript,urlencode()in PHP) when building URLs dynamically, rather than constructing them by hand.
Examples
Literal percent sign not encoded
<!-- ❌ Bad: bare "%" is not followed by two hex digits -->
<img alt="Chart" src="https://example.com/chart.png?scale=50%">
<!-- ✅ Fixed: "%" encoded as "%25" -->
<img alt="Chart" src="https://example.com/chart.png?scale=50%25">
Incomplete percent-encoding sequence
<!-- ❌ Bad: "%2" is incomplete — missing the second hex digit -->
<img alt="Photo" src="https://example.com/my%2photo.jpg">
<!-- ✅ Fixed: use "%20" for a space character -->
<img alt="Photo" src="https://example.com/my%20photo.jpg">
Non-hexadecimal characters after percent sign
<!-- ❌ Bad: "%zz" uses non-hex characters -->
<img alt="Logo" src="https://example.com/logo%zz.png">
<!-- ✅ Fixed: remove the erroneous sequence if it was unintentional -->
<img alt="Logo" src="https://example.com/logo.png">
Multiple encoding issues in one URL
<!-- ❌ Bad: bare "%" in query value and unencoded space -->
<img alt="Report" src="https://example.com/img?label=100%&name=my file.png">
<!-- ✅ Fixed: "%" → "%25", space → "%20" -->
<img alt="Report" src="https://example.com/img?label=100%25&name=my%20file.png">
Building URLs safely with JavaScript
When generating src values in code, use encodeURIComponent() to handle special characters automatically:
const label = "100%";
const name = "my file.png";
const src = `https://example.com/img?label=${encodeURIComponent(label)}&name=${encodeURIComponent(name)}`;
// Result: "https://example.com/img?label=100%25&name=my%20file.png"
This ensures every special character — including % — is correctly percent-encoded without manual effort.
The fragment portion of a URL (everything after the # symbol) follows the same encoding rules as the rest of the URL — literal space characters are not permitted. When a browser encounters a space in a URL fragment, it may try to correct it automatically, but this behavior is not guaranteed across all browsers and contexts. Relying on browser error-correction leads to fragile links that may break unpredictably.
This issue matters for several reasons. First, it produces invalid HTML that fails W3C validation. Second, fragment navigation (jumping to a specific section of a page) may not work correctly if the browser doesn't auto-correct the space. Third, assistive technologies like screen readers rely on well-formed URLs to announce link destinations accurately. Finally, tools that parse or process HTML programmatically — such as crawlers, link checkers, and content management systems — may misinterpret or reject malformed URLs.
The most common scenario for this error is linking to an id attribute on the same page or another page where the id contains spaces. However, it's worth noting that id values themselves cannot contain spaces in valid HTML (a space in an id would make it multiple invalid tokens). So if you're writing both the link and the target, the best fix is often to correct the id itself by using hyphens or underscores instead of spaces.
How to Fix It
There are two main approaches:
- Percent-encode the spaces — Replace each space with
%20in thehrefvalue. - Use space-free identifiers — Change the target
idto use hyphens or camelCase, then update the fragment to match.
The second approach is strongly preferred because it fixes the root problem and produces cleaner, more readable markup.
Examples
❌ Invalid: Space in the fragment
<a href="https://example.com/page#some term">Go to section</a>
<a href="#my section">Jump to My Section</a>
✅ Fixed: Percent-encode the space
If you cannot control the target id (e.g., linking to an external page), percent-encode the space:
<a href="https://example.com/page#some%20term">Go to section</a>
✅ Better fix: Use a hyphenated id and matching fragment
When you control both the link and the target, use a space-free id:
<h2 id="my-section">My Section</h2>
<p>Some content here.</p>
<a href="#my-section">Jump to My Section</a>
❌ Invalid: Space in fragment linking to another page
<a href="/docs/getting-started#quick start guide">Quick Start Guide</a>
✅ Fixed: Matching hyphenated id
<!-- On the target page (/docs/getting-started): -->
<h2 id="quick-start-guide">Quick Start Guide</h2>
<!-- On the linking page: -->
<a href="/docs/getting-started#quick-start-guide">Quick Start Guide</a>
A note on id naming conventions
Since fragment identifiers reference id attributes, adopting a consistent id naming convention avoids this issue entirely. Common patterns include:
<!-- Hyphens (most common, used by many static site generators) -->
<section id="getting-started">
<!-- camelCase -->
<section id="gettingStarted">
<!-- Underscores -->
<section id="getting_started">
All three are valid and will never trigger a space-related validation error in your fragment links.
A < character inside an href attribute is not valid in a URL and must be removed or percent-encoded.
The mailto: URL scheme expects a well formed email address directly after the colon, such as mailto:user@example.com. The angle brackets (< and >) sometimes seen around email addresses in mail headers or plain text are not part of the URL syntax. Including them in the href value produces an illegal character error because < and > are not permitted in URIs without percent-encoding.
If angle brackets appear in your markup, they likely got there by copying an address from an email header like From: User <user@example.com> and pasting the whole thing into the link. Strip the brackets so only the bare address remains.
HTML examples
Incorrect
<a href="mailto:<user@example.com>">Email us</a>
Correct
<a href="mailto:user@example.com">Email us</a>
A URL in an href attribute contains a character that must be percent-encoded before it can appear in a query string.
URLs follow the syntax rules defined in RFC 3986. The query component of a URL (everything after the ?) allows a specific set of characters: unreserved characters (A-Z, a-z, 0-9, -, ., _, ~), sub-delimiters (!, $, &, ', (, ), *, +, ,, ;, =), plus :, @, /, and ?. Characters outside this set, such as {, }, |, ^, [, ], spaces, and non-ASCII characters like é or ñ, are illegal in their raw form and must be percent-encoded.
Percent-encoding replaces each disallowed byte with a % followed by its two-digit hexadecimal value. For example, a space becomes %20, a pipe | becomes %7C, and { becomes %7B.
Common causes of this error include:
- Pasting a URL directly from a browser address bar or CMS that displays decoded characters.
- Using curly braces for template placeholders in URLs without encoding them.
- Including spaces or non-ASCII characters in query parameter values.
HTML examples
Invalid: raw illegal characters in the query string
<a href="https://example.com/search?q=hello world&cat=sci|tech">Search</a>
The space between hello and world and the pipe | between sci and tech are not allowed in a URL query.
Valid: percent-encoded characters
<a href="https://example.com/search?q=hello%20world&cat=sci%7Ctech">Search</a>
Each illegal character is replaced with its percent-encoded equivalent: space is %20 and | is %7C.
A link element's href attribute contains a character that is not valid in a URL, such as a space, curly brace, or other unencoded special character.
URLs in HTML must conform to the URL Living Standard. Certain characters are not allowed to appear literally in a URL and must be percent-encoded. Common offenders include spaces (use %20), curly braces { } (use %7B %7D), pipe | (use %7C), and angle brackets < > (use %3C %3E).
This error often appears when a template placeholder like {{variable}} is left unresolved in the href value, or when a URL is copied from another context and contains unencoded characters. The validator reads the raw HTML source, so even if a browser might handle a malformed URL gracefully, the markup is still invalid.
To fix it, replace every illegal character with its percent-encoded equivalent, or remove the character if it does not belong in the URL.
Examples
Invalid: unencoded characters in href
<link rel="stylesheet" href="https://example.com/styles/main file.css">
The space between main and file is not allowed in a URL.
Valid: percent-encoded URL
<link rel="stylesheet" href="https://example.com/styles/main%20file.css">
If the illegal characters come from a template placeholder that was never processed, remove the placeholder or ensure it resolves before the HTML is served:
Invalid: unresolved template syntax
<link rel="icon" href="https://example.com/{{icon_path}}">
Valid: resolved or corrected URL
<link rel="icon" href="https://example.com/images/favicon.ico">
The <icon> element does not exist in HTML. No version of the HTML specification defines it, and browsers do not recognize it.
This error appears when markup includes <icon> as if it were a standard HTML element. It is not. Browsers will treat it as an unknown inline element with no default behavior or semantics. To display icons, use an <img> element, an <svg> element, or a <span> with CSS-applied background images or icon fonts.
If the intent is to define a favicon (the small icon shown in browser tabs), the correct approach is a <link> element inside <head> with rel="icon".
HTML examples
Invalid: using the <icon> element
<head>
<title>My page</title>
<icon src="favicon.png"></icon>
</head>
Valid: using a <link> element for a favicon
<head>
<title>My page</title>
<link rel="icon" href="favicon.png" type="image/png">
</head>
Valid: displaying an icon inline with <img>
<p>
<img src="star.svg" alt="Star" width="16" height="16"> Favorite
</p>
Valid: displaying an icon inline with <span> and CSS
<p>
<span class="icon icon-star" aria-hidden="true"></span> Favorite
</p>
A tel: URI in an href attribute must not contain spaces. The phone number should be written as a continuous string of digits, optionally prefixed with + for the country code.
The tel: URI scheme is defined in RFC 3966. It allows digits, hyphens, dots, and parentheses as visual separators, but spaces are not permitted. Browsers may still dial the number if spaces are present, but the markup is technically invalid.
A common approach is to use the full international number with no spaces in the href value while keeping a human-readable format in the visible link text. If you want visual separators in the href, use hyphens or dots instead of spaces.
Invalid example
<a href="tel:+1 555 123 4567">Call us</a>
Valid examples
<a href="tel:+15551234567">+1 555 123 4567</a>
<a href="tel:+1-555-123-4567">+1 555 123 4567</a>
Square brackets ([ and ]) are not valid characters in a URL path and must be percent-encoded.
The HTML specification requires that the href attribute on an <a> element contains a valid URL. According to RFC 3986, square brackets are reserved characters that are only permitted in the host component of a URI (specifically for IPv6 addresses like [::1]). When they appear in the path, query, or fragment components, they must be percent-encoded as %5B for [ and %5D for ].
This commonly happens with URLs that contain array-like query parameters (e.g., filter[status]=active) or paths that include brackets for some framework-specific routing convention.
To fix the issue, replace every [ with %5B and every ] with %5D in the URL path or query string.
HTML examples
Invalid: square brackets in the URL
<a href="/products?filter[color]=red&filter[size]=large">
Red, large products
</a>
Valid: percent-encoded square brackets
<a href="/products?filter%5Bcolor%5D=red&filter%5Bsize%5D=large">
Red, large products
</a>
The encoded version is functionally equivalent. Most web servers and frameworks will decode %5B and %5D back to [ and ] automatically when processing the request.
A < character in an href attribute value is not allowed because it is a reserved character in URLs and must be percent-encoded.
The href attribute on an <a> element must contain a valid URL or URL fragment. Characters like <, >, {, }, and others are not permitted directly in URLs according to RFC 3986. The < character is especially problematic because browsers and parsers interpret it as the start of an HTML tag, which can break your markup and introduce security risks like XSS vulnerabilities.
This error commonly appears when template syntax (e.g., {{variable}}), unescaped user input, or malformed URLs end up inside an href value. If you genuinely need a < in a URL, it must be percent-encoded as %3C. Similarly, > should be encoded as %3E.
Example with the issue
<a href="https://example.com/search?q=<value>">Search</a>
How to fix it
Percent-encode the special characters in the URL:
<a href="https://example.com/search?q=%3Cvalue%3E">Search</a>
If you're generating URLs dynamically on the server side, use your language's built-in URL encoding function (e.g., encodeURIComponent() in JavaScript, urlencode() in PHP) to ensure all special characters are properly escaped before inserting them into href attributes.
The W3C HTML Validator checks that URLs provided in href attributes conform to the URL specification. Square brackets ([ and ]) are reserved characters with very specific, limited uses in URLs — they are only permitted in the host portion of a URL to denote IPv6 addresses (e.g., http://[::1]/). When they appear elsewhere, such as in the scheme data, path, or query string without being percent-encoded, the URL is considered malformed.
This commonly happens in a few scenarios:
- Mailto links where someone wraps an email address in brackets, like
mailto:[user@example.com]. - Template variables that haven't been processed, leaving literal bracket syntax (e.g.,
{{,[,]) in the rendered HTML. - Manually constructed URLs where brackets are mistakenly used as part of the path or query string instead of being percent-encoded as
%5Band%5D.
Using invalid URLs can cause browsers to misinterpret the link destination, break navigation, or cause unexpected behavior. Assistive technologies such as screen readers also rely on well-formed URLs to correctly communicate link targets to users. Keeping your URLs standards-compliant ensures consistent, predictable behavior across all browsers and devices.
How to fix it
- Remove unnecessary brackets. If the brackets are not part of the actual data (e.g., decorative brackets around an email address), simply delete them.
- Percent-encode brackets when they are part of the data. If you genuinely need square brackets in a URL's path or query string, encode
[as%5Band]as%5D. - Check your templating engine output. If you use a templating system, inspect the final rendered HTML in a browser to make sure template syntax has been fully replaced with valid values.
Examples
Invalid: square brackets in a mailto URL
The brackets around the email address are not valid URL characters in this context.
<a href="mailto:[user@example.com]">Email Us</a>
Fixed: remove the brackets
<a href="mailto:user@example.com">Email Us</a>
Invalid: square brackets in a query string
<a href="https://example.com/search?filter[status]=active">Search</a>
Fixed: percent-encode the brackets
<a href="https://example.com/search?filter%5Bstatus%5D=active">Search</a>
Invalid: unprocessed template syntax in rendered HTML
If your templating engine fails to replace a variable, the final HTML may contain bracket characters:
<a href="mailto:[% user.email %]">Email Us</a>
Fixed: ensure the template renders a valid URL
Make sure the template variable resolves correctly. The rendered output should look like:
<a href="mailto:user@example.com">Email Us</a>
In your template source, this might be written as:
<a href="mailto:{{ user.email }}">Email Us</a>
The key is that the final HTML delivered to the browser must contain a valid, bracket-free URL (unless the brackets are properly percent-encoded). Always validate your rendered output, not just your template source, to catch issues like this.
A space character in the src attribute of an iframe is not valid in a URL. Spaces must be percent-encoded as %20 to produce a legal URL.
URLs follow the syntax rules defined in RFC 3986, which does not allow literal space characters anywhere in a URI, including the query string portion (the part after ?). When the HTML validator encounters a raw space in a URL attribute like src, it flags the value as malformed.
This commonly happens when query parameter values contain readable text, or when a URL is copied from a browser's address bar where the browser displays decoded spaces for readability. The fix is to replace each space with %20.
Some developers use + instead of %20 for spaces in query strings. The + convention originates from the application/x-www-form-urlencoded format used in HTML form submissions, and many servers accept it. However, %20 is the correct encoding according to RFC 3986 and will pass W3C validation without issues.
HTML examples
Invalid: space in the query string
<iframe
src="https://example.com/embed?title=my page&lang=en"
width="600"
height="400">
</iframe>
Valid: space encoded as %20
<iframe
src="https://example.com/embed?title=my%20page&lang=en"
width="600"
height="400">
</iframe>
Square brackets are not valid characters in a URL path segment and must be percent-encoded in the poster attribute of a <video> element.
The poster attribute accepts a valid URL that points to an image displayed while the video is not playing or until the first frame loads. URLs follow RFC 3986, which restricts the characters allowed in path segments. Square brackets ([ and ]) are reserved characters that are only permitted in the host component of a URL (for IPv6 addresses). When they appear in a path, query, or fragment component, they must be percent-encoded as %5B for [ and %5D for ].
This applies to any HTML attribute that expects a URL, not just poster. The same rule covers src, href, action, and similar attributes.
Invalid example
<video poster="/images/thumbnail[1].jpg" controls>
<source src="video.mp4" type="video/mp4">
</video>
Fixed example
Replace [ with %5B and ] with %5D:
<video poster="/images/thumbnail%5B1%5D.jpg" controls>
<source src="video.mp4" type="video/mp4">
</video>
If you control the file names on the server, renaming the file to avoid square brackets entirely is a simpler long term fix:
<video poster="/images/thumbnail-1.jpg" controls>
<source src="video.mp4" type="video/mp4">
</video>
URLs follow strict syntax rules defined by RFC 3986, which does not permit literal space characters anywhere in a URI. When the W3C validator encounters a space in the href attribute of an <a> element — particularly within the query string (the part after the ?) — it flags it as an illegal character.
While most modern browsers will silently fix malformed URLs by encoding spaces for you, relying on this behavior is problematic for several reasons:
- Standards compliance: The HTML specification requires that
hrefvalues contain valid URLs. A space makes the URL syntactically invalid. - Interoperability: Not all user agents, crawlers, or HTTP clients handle malformed URLs the same way. Some may truncate the URL at the first space or reject it entirely.
- Accessibility: Screen readers and assistive technologies may struggle to interpret or announce links with invalid URLs.
- Link sharing and copy-pasting: If a user copies the link from the source or if the URL is used in an API or redirect, the unencoded space can cause breakage.
To fix this issue, replace every literal space character in the URL with %20. Within query string values, you can also use + as a space encoding (this is the application/x-www-form-urlencoded format commonly used in form submissions). If you're generating URLs dynamically, use your programming language's URL encoding function (e.g., encodeURIComponent() in JavaScript, urlencode() in PHP, or urllib.parse.quote() in Python).
Examples
Incorrect — spaces in the query string
<a href="https://example.com/search?query=hello world&lang=en">Search</a>
The space between hello and world is an illegal character in the URL.
Correct — space encoded as %20
<a href="https://example.com/search?query=hello%20world&lang=en">Search</a>
Correct — space encoded as + in query string
<a href="https://example.com/search?query=hello+world&lang=en">Search</a>
Using + to represent a space is valid within query strings and is commonly seen in URLs generated by HTML forms.
Incorrect — spaces in the path and query
<a href="https://example.com/my folder/page?name=John Doe">Profile</a>
Correct — all spaces properly encoded
<a href="https://example.com/my%20folder/page?name=John%20Doe">Profile</a>
Generating safe URLs in JavaScript
If you're building URLs dynamically, use encodeURIComponent() for individual parameter values:
<script>
const query = "hello world";
const url = "https://example.com/search?query=" + encodeURIComponent(query);
// Result: "https://example.com/search?query=hello%20world"
</script>
Note that encodeURIComponent() encodes spaces as %20, which is safe for use in any part of a URL. Avoid using encodeURI() for query values, as it does not encode certain characters like & and = that may conflict with query string syntax.
The ARIA specification defines a strict ownership hierarchy for table-related roles. A role="cell" element must be "owned by" an element with role="row", meaning it must either be a direct child of that element or be associated with it via the aria-owns attribute. This mirrors how native HTML tables work: a <td> element must live inside a <tr> element. When you use ARIA roles to build custom table structures from non-table elements like <div> or <span>, you are responsible for maintaining this same hierarchy manually.
The expected nesting order for an ARIA table is:
role="table"— the outermost containerrole="rowgroup"(optional) — groups rows together, like<thead>,<tbody>, or<tfoot>role="row"— a single row of cellsrole="cell"orrole="columnheader"/role="rowheader"— individual cells
When a role="cell" element is placed directly inside a role="table" or any other container that isn't role="row", screen readers lose the ability to announce row and column positions. Users who rely on table navigation shortcuts (such as moving between cells with arrow keys) will find the table unusable. This is not just a validation concern — it directly impacts whether people can access your content.
This issue commonly arises when developers add intermediate wrapper elements for styling purposes and accidentally break the required parent-child relationship, or when they forget to assign role="row" to a container element.
How to Fix
Ensure every element with role="cell" is a direct child of an element with role="row". If you have wrapper elements between the row and cell for layout or styling, either remove them, move the role assignments, or use aria-owns on the row element to explicitly claim ownership of the cells.
Examples
Incorrect — Cell Without a Row Parent
This triggers the validation error because role="cell" elements are direct children of the role="table" container, with no role="row" in between.
<div role="table">
<div role="cell">Name</div>
<div role="cell">Email</div>
</div>
Incorrect — Intermediate Wrapper Breaking Ownership
Here, a styling wrapper sits between the row and its cells. Since the <div> without a role is not a role="row", the cells are not properly owned.
<div role="table">
<div role="row">
<div class="cell-wrapper">
<div role="cell">Row 1, Cell 1</div>
<div role="cell">Row 1, Cell 2</div>
</div>
</div>
</div>
Correct — Cells Directly Inside Rows
Each role="cell" is a direct child of a role="row" element, forming a valid ARIA table structure.
<div role="table" aria-label="Team members">
<div role="row">
<div role="columnheader">Name</div>
<div role="columnheader">Email</div>
</div>
<div role="row">
<div role="cell">Alice</div>
<div role="cell">alice@example.com</div>
</div>
<div role="row">
<div role="cell">Bob</div>
<div role="cell">bob@example.com</div>
</div>
</div>
Correct — Using Rowgroups
You can optionally group rows with role="rowgroup", similar to <thead> and <tbody>. The cells still must be direct children of their rows.
<div role="table" aria-label="Quarterly results">
<div role="rowgroup">
<div role="row">
<div role="columnheader">Quarter</div>
<div role="columnheader">Revenue</div>
</div>
</div>
<div role="rowgroup">
<div role="row">
<div role="cell">Q1</div>
<div role="cell">$10,000</div>
</div>
<div role="row">
<div role="cell">Q2</div>
<div role="cell">$12,500</div>
</div>
</div>
</div>
Correct — Using Native HTML Instead
If your content is genuinely tabular data, consider using native HTML table elements instead of ARIA roles. Native tables have built-in semantics and require no additional role attributes.
<table>
<tr>
<th>Name</th>
<th>Email</th>
</tr>
<tr>
<td>Alice</td>
<td>alice@example.com</td>
</tr>
</table>
Native HTML tables are always preferable when they suit your use case. The first rule of ARIA is: if you can use a native HTML element that already has the semantics you need, use it instead of adding ARIA roles to a generic element.
Percent-encoding (also called URL encoding) is the mechanism defined by RFC 3986 for representing special or reserved characters in URLs. It works by replacing a character with a % sign followed by two hexadecimal digits that correspond to the character's byte value. For example, a space becomes %20, an ampersand becomes %26, and a literal percent sign becomes %25. When a browser or parser encounters % in a URL, it expects the next two characters to be valid hexadecimal digits so it can decode them back to the original character.
If the parser finds a % that is not followed by two hexadecimal digits, it cannot decode the sequence, and the URL is considered invalid. The W3C validator flags this as a bad value for the href attribute.
Common causes
- A literal
%that wasn't encoded. This is the most frequent cause. For instance, a URL containing a percentage value like50%in a query string — the%must be written as%25. - Truncated percent-encoded sequences. A sequence like
%2is incomplete because it only has one hex digit instead of two. - Invalid hex characters after
%. A sequence like%GZis invalid becauseGandZare not hexadecimal digits (valid hex digits are0–9andA–F). - Copy-pasting URLs from non-web sources. URLs pasted from documents, emails, or spreadsheets may contain unencoded special characters.
Why this matters
- Broken links. Browsers may interpret the malformed URL differently than intended, leading users to the wrong page or a 404 error.
- Standards compliance. The WHATWG URL Standard and the HTML specification require that
hrefvalues contain valid URLs. Malformed URLs violate these standards. - Accessibility. Screen readers and assistive technologies rely on well-formed URLs to provide accurate navigation information to users.
- Interoperability. While some browsers attempt error recovery on malformed URLs, behavior is not guaranteed to be consistent across all browsers and tools.
How to fix it
- Encode literal
%as%25. If the%is meant to appear as part of the URL's content (e.g., in a query parameter value), replace it with%25. - Complete any truncated sequences. If you intended a percent-encoded character like
%20, make sure both hex digits are present. - Use proper URL encoding functions. In JavaScript, use
encodeURIComponent()for query parameter values. In server-side languages, use the equivalent encoding function (e.g.,urllib.parse.quote()in Python,urlencode()in PHP).
Examples
Literal % not encoded
<!-- ❌ Bad: the % in "48%" is not followed by two hex digits -->
<a href="https://example.com?width=48%">48% width</a>
<!-- ✅ Good: the % is encoded as %25 -->
<a href="https://example.com?width=48%25">48% width</a>
Truncated percent-encoded sequence
<!-- ❌ Bad: %2 is incomplete — it needs two hex digits -->
<a href="https://example.com/path%2file">Download</a>
<!-- ✅ Good: %2F is a complete encoding for "/" -->
<a href="https://example.com/path%2Ffile">Download</a>
Invalid hexadecimal characters
<!-- ❌ Bad: %XY contains non-hexadecimal characters -->
<a href="https://example.com/search?q=%XYdata">Search</a>
<!-- ✅ Good: if the intent was a literal "%XY", encode the % -->
<a href="https://example.com/search?q=%25XYdata">Search</a>
Multiple unencoded % in a URL
<!-- ❌ Bad: both % signs are unencoded -->
<a href="https://example.com?min=10%&max=90%">Range</a>
<!-- ✅ Good: both % signs are properly encoded -->
<a href="https://example.com?min=10%25&max=90%25">Range</a>
The srcset attribute allows you to specify multiple image sources so the browser can choose the most appropriate one based on the user's device characteristics, such as screen resolution or viewport width. When you include a srcset attribute on an <img> element, the HTML specification requires it to contain one or more comma-separated image candidate strings. Each string consists of a URL followed by an optional descriptor — either a width descriptor (e.g., 200w) or a pixel density descriptor (e.g., 2x).
This validation error typically appears when:
- The
srcsetattribute is empty (srcset="") - The
srcsetattribute contains only whitespace (srcset=" ") - The value contains syntax errors such as missing URLs, invalid descriptors, or incorrect formatting
- A templating engine or CMS outputs the attribute with no value
This matters because browsers rely on the srcset value to select the best image to load. An empty or malformed srcset means the browser must fall back entirely to the src attribute, making the srcset attribute pointless. Additionally, invalid markup can cause unexpected behavior across different browsers and undermines standards compliance.
How to fix it
- Provide valid image candidate strings. Each entry needs a URL and optionally a width or pixel density descriptor, with entries separated by commas.
- Remove the attribute entirely if you don't have multiple image sources to offer. A plain
srcattribute is perfectly fine on its own. - Check dynamic output. If a CMS or templating system generates the
srcset, ensure it conditionally omits the attribute when no responsive image candidates are available, rather than outputting an empty attribute.
Examples
❌ Empty srcset attribute
<img src="/img/photo.jpg" alt="A sunset over the ocean" srcset="">
This triggers the error because srcset is present but contains no image candidate strings.
❌ Malformed srcset value
<img src="/img/photo.jpg" alt="A sunset over the ocean" srcset="1x, 2x">
This is invalid because each candidate string must include a URL. Descriptors alone are not valid entries.
✅ Using pixel density descriptors
<img
src="/img/photo-400.jpg"
alt="A sunset over the ocean"
srcset="
/img/photo-400.jpg 1x,
/img/photo-800.jpg 2x
">
Each candidate string contains a URL followed by a pixel density descriptor (1x, 2x). The browser picks the best match for the user's display.
✅ Using width descriptors with sizes
<img
src="/img/photo-400.jpg"
alt="A sunset over the ocean"
srcset="
/img/photo-400.jpg 400w,
/img/photo-800.jpg 800w,
/img/photo-1200.jpg 1200w
"
sizes="(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px">
Width descriptors (e.g., 400w) tell the browser the intrinsic width of each image. The sizes attribute then tells the browser how large the image will be displayed at various viewport sizes, allowing it to calculate the best source to download.
✅ Removing srcset when not needed
<img src="/img/photo.jpg" alt="A sunset over the ocean">
If you only have a single image source, simply omit srcset altogether. The src attribute alone is valid and sufficient.
✅ Single candidate in srcset
<img
src="/img/photo.jpg"
alt="A sunset over the ocean"
srcset="/img/photo-highres.jpg 2x">
Even a single image candidate string is valid. Here, the browser will use the high-resolution image on 2x displays and fall back to src otherwise.
The <tabs> element does not exist in the HTML specification and is not a valid HTML element.
Browsers parse unknown elements like <tabs> as generic inline elements with no semantics. Screen readers and other assistive technologies cannot interpret them correctly, so users who rely on those tools get no meaningful information about the content's purpose or structure.
If the goal is to create a tabbed interface, use standard HTML elements with appropriate ARIA roles. The WAI-ARIA Authoring Practices describe a well established tabs pattern built from <div>, <button>, and role attributes. Each tab is a <button> with role="tab", grouped inside a container with role="tablist". Each panel is a <div> with role="tabpanel".
HTML examples
Invalid: unknown <tabs> element
<tabs>
<tab>Tab 1</tab>
<tab>Tab 2</tab>
<tab-panel>Content for tab 1</tab-panel>
<tab-panel>Content for tab 2</tab-panel>
</tabs>
Valid: ARIA tabs pattern with standard elements
<div role="tablist" aria-label="Sample tabs">
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">
Tab 1
</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2" tabindex="-1">
Tab 2
</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
<p>Content for tab 1</p>
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
<p>Content for tab 2</p>
</div>
In this pattern, aria-selected indicates the active tab, aria-controls links each tab to its panel, and aria-labelledby links each panel back to its tab. The hidden attribute hides inactive panels. JavaScript is needed to toggle aria-selected, tabindex, and hidden when the user switches tabs.
The dir attribute specifies the base text direction for the content of an element. When a document is written in a right-to-left language like Arabic, Hebrew, Persian (Farsi), or Urdu, the browser needs to know that text should flow from right to left. Without this attribute, browsers default to left-to-right (ltr) rendering, which can cause a range of problems: text alignment may be incorrect, punctuation can appear in the wrong place, and the overall page layout may look broken or confusing to native readers.
This matters for several important reasons:
- Accessibility: Screen readers and other assistive technologies rely on the
dirattribute to correctly announce and navigate content. Without it, the reading experience for users relying on these tools may be disorienting or incorrect. - Visual correctness: Elements like lists, tables, form labels, and navigation menus will mirror their layout in RTL mode. Without
dir="rtl", these elements default to LTR positioning, which feels unnatural for RTL language speakers. - Bidirectional text handling: Documents often contain mixed-direction content (e.g., Arabic text with embedded English words, numbers, or brand names). The Unicode Bidirectional Algorithm (BiDi) uses the base direction set by
dirto correctly resolve the ordering of these mixed runs of text. - Standards compliance: The WHATWG HTML Living Standard recommends that authors set the
dirattribute to match the language of the document, and the W3C Internationalization guidelines strongly encourage it for RTL languages.
How to fix it
Add dir="rtl" to your <html> start tag. If you haven't already, also include the appropriate lang attribute for the specific language you're using.
For Arabic:
<html dir="rtl" lang="ar">
For Hebrew:
<html dir="rtl" lang="he">
For Persian:
<html dir="rtl" lang="fa">
Examples
❌ Missing dir attribute on an Arabic document
<!DOCTYPE html>
<html lang="ar">
<head>
<meta charset="utf-8">
<title>مرحبا بالعالم</title>
</head>
<body>
<h1>مرحبا بالعالم</h1>
<p>هذه صفحة تجريبية باللغة العربية.</p>
</body>
</html>
The browser will render this page with a left-to-right base direction. The heading and paragraph text may appear left-aligned, and any mixed-direction content (like embedded numbers or English words) may be ordered incorrectly.
✅ Correct: dir="rtl" added to the <html> tag
<!DOCTYPE html>
<html dir="rtl" lang="ar">
<head>
<meta charset="utf-8">
<title>مرحبا بالعالم</title>
</head>
<body>
<h1>مرحبا بالعالم</h1>
<p>هذه صفحة تجريبية باللغة العربية.</p>
</body>
</html>
Now the browser knows to render the page right-to-left. Text will be right-aligned by default, scroll bars will appear on the left, and the overall layout will feel natural for Arabic readers.
Handling mixed-direction content within a page
If your RTL document contains sections in a left-to-right language, use the dir attribute on individual elements to override the base direction locally:
<p>قام المستخدم بزيارة <span dir="ltr">www.example.com</span> اليوم.</p>
This ensures the URL renders in the correct left-to-right order while the surrounding Arabic text flows right-to-left. Setting dir="rtl" on the <html> element establishes the correct default, and you only need per-element overrides for embedded LTR content.
The <o:p> element is a Microsoft Office namespace tag that has no meaning in HTML and is not part of any HTML specification.
When content is copied from Microsoft Word, Outlook, or other Office applications and pasted into an HTML document, the source often includes proprietary XML namespace elements like <o:p>, <o:OfficeDocumentSettings>, and similar tags. These belong to the urn:schemas-microsoft-com:office:office namespace and are only understood by Office applications. Browsers ignore them, but the W3C validator flags them as errors because they are not valid HTML elements.
The <o:p> tag specifically is used by Word to wrap paragraph content. It typically appears inside <p> elements and most often contains nothing or just a non-breaking space ( ). Removing it entirely has no effect on the rendered page.
If the <o:p> element wraps actual text content, replace it with a standard HTML element like <span> or simply keep the text without any wrapper. If it is empty or contains only , delete it.
HTML examples
Invalid: Office namespace element in HTML
<p>This is a paragraph.<o:p></o:p></p>
<p>
<o:p> </o:p>
</p>
Valid: Office elements removed
<p>This is a paragraph.</p>
When cleaning up Office-generated HTML, also look for other common namespace prefixes like <w:, <m:, and <v:, along with mso- prefixed CSS properties in style attributes. These are all Office artifacts and should be removed. Many text editors and CMS platforms have a "Paste as plain text" option that strips this markup automatically.
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